1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-15 23:52:33 +01:00

Merge pull request #6303 from turbo124/v5-stable

v5.2.14
This commit is contained in:
David Bomba 2021-07-21 09:15:11 +10:00 committed by GitHub
commit 65e78825fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 12669 additions and 136 deletions

View File

@ -1 +1 @@
5.2.13
5.2.14

View File

@ -663,6 +663,7 @@ class CompanySettings extends BaseSettings
'$task.line_total',
],
'total_columns' => [
'$net_subtotal',
'$subtotal',
'$discount',
'$custom_surcharge1',

View File

@ -16,6 +16,7 @@ use App\DataMapper\Analytics\LoginSuccess;
use App\Events\User\UserLoggedIn;
use App\Http\Controllers\BaseController;
use App\Http\Controllers\Controller;
use App\Http\Requests\Login\LoginRequest;
use App\Jobs\Account\CreateAccount;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Util\SystemLogger;
@ -156,7 +157,7 @@ class LoginController extends BaseController
* ),
* )
*/
public function apiLogin(Request $request)
public function apiLogin(LoginRequest $request)
{
$this->forced_includes = ['company_users'];

View File

@ -25,7 +25,10 @@ use App\Jobs\Company\CreateCompany;
use App\Jobs\Company\CreateCompanyPaymentTerms;
use App\Jobs\Company\CreateCompanyTaskStatuses;
use App\Jobs\Company\CreateCompanyToken;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Jobs\Ninja\RefundCancelledAccount;
use App\Mail\Company\CompanyDeleted;
use App\Models\Account;
use App\Models\Company;
use App\Models\CompanyUser;
@ -504,6 +507,15 @@ class CompanyController extends BaseController
$company->company_users->each(function ($company_user){
$company_user->forceDelete();
});
$other_company = $company->account->companies->where('id', '!=', $company->id)->first();
$nmo = new NinjaMailerObject;
$nmo->mailable = new CompanyDeleted($company->present()->name, auth()->user(), $company->account, $company->settings);
$nmo->company = $other_company;
$nmo->settings = $other_company->settings;
$nmo->to_user = auth()->user();
NinjaMailerJob::dispatch($nmo);
$company->delete();

View File

@ -35,7 +35,7 @@ class StripeController extends BaseController
public function import()
{
return response()->json(['message' => 'Processing'], 200);
// return response()->json(['message' => 'Processing'], 200);
if(auth()->user()->isAdmin())

View File

@ -44,6 +44,14 @@ class PasswordProtection
else
$timeout = $timeout/1000;
//test if password if base64 encoded
$x_api_password = $request->header('X-API-PASSWORD');
if($request->header('X-API-PASSWORD-BASE64'))
{
$x_api_password = base64_decode($request->header('X-API-PASSWORD-BASE64'));
}
if (Cache::get(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in')) {
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);
@ -66,7 +74,7 @@ class PasswordProtection
];
//If OAuth and user also has a password set - check both
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $request->header('X-API-PASSWORD'))) {
if ($existing_user = MultiDB::hasUser($query) && auth()->user()->company()->oauth_password_required && auth()->user()->has_password && Hash::check(auth()->user()->password, $x_api_password)) {
nlog("existing user with password");
@ -86,7 +94,7 @@ class PasswordProtection
return response()->json($error, 412);
}elseif ($request->header('X-API-PASSWORD') && Hash::check($request->header('X-API-PASSWORD'), auth()->user()->password)) {
}elseif ($x_api_password && Hash::check($x_api_password, auth()->user()->password)) {
Cache::put(auth()->user()->hashed_id.'_'.auth()->user()->account_id.'_logged_in', Str::random(64), $timeout);

View File

@ -144,7 +144,10 @@ class StoreClientRequest extends Request
return $item->iso_3166_2 == $country_code || $item->iso_3166_3 == $country_code;
})->first();
return (string) $country->id;
if($country)
return (string) $country->id;
return "";
}
private function getCurrencyCode($code)

View File

@ -0,0 +1,54 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Login;
use App\Http\Requests\Request;
class LoginRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required',
'password' => 'required',
];
}
protected function prepareForValidation()
{
$input = $this->all();
// if(base64_decode(base64_encode($input['password'])) === $input['password'])
// $input['password'] = base64_decode($input['password']);
// nlog($input['password']);
$this->replace($input);
}
}

View File

@ -46,7 +46,7 @@ class ValidRefundableRequest implements Rule
return false;
}
$payment = Payment::whereId($this->input['id'])->first();
$payment = Payment::whereId($this->input['id'])->withTrashed()->first();
if (! $payment) {
$this->error_msg = ctrans('texts.unable_to_retrieve_payment');
@ -77,7 +77,7 @@ class ValidRefundableRequest implements Rule
private function checkInvoiceIsPaymentable($invoice, $payment)
{
$invoice = Invoice::whereId($invoice['invoice_id'])->whereCompanyId($payment->company_id)->first();
$invoice = Invoice::whereId($invoice['invoice_id'])->whereCompanyId($payment->company_id)->withTrashed()->first();
if ($payment->invoices()->exists()) {
$paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first();

View File

@ -79,12 +79,14 @@ class CreateAccount
if(Ninja::isHosted())
{
$sp794f3f->trial_started = now();
$sp794f3f->trial_plan = 'pro';
// $sp794f3f->plan = 'pro';
$sp794f3f->save();
}
$sp794f3f->save();
$sp035a66 = CreateCompany::dispatchNow($this->request, $sp794f3f);
$sp035a66->load('account');
$sp794f3f->default_company_id = $sp035a66->id;

View File

@ -1139,7 +1139,10 @@ class Import implements ShouldQueue
$payment->save(['timestamps' => false]);
if (array_key_exists('company_gateway_id', $resource) && isset($resource['company_gateway_id']) && $resource['company_gateway_id'] != 'NULL') {
$payment->company_gateway_id = $this->transformId('company_gateways', $resource['company_gateway_id']);
if($this->tryTransformingId('company_gateways', $resource['company_gateway_id']))
$payment->company_gateway_id = $this->transformId('company_gateways', $resource['company_gateway_id']);
$payment->save();
}

View File

@ -71,18 +71,20 @@ class ReminderJob implements ShouldQueue
if ($invoice->isPayable()) {
$reminder_template = $invoice->calculateTemplate('invoice');
$invoice->service()->touchReminder($reminder_template)->save();
$invoice = $this->calcLateFee($invoice, $reminder_template);
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
nlog("Firing reminder email for invoice {$invoice->number}");
});
//check if this reminder needs to be emailed
if(in_array($reminder_template, ['reminder1','reminder2','reminder3']) && $invoice->client->getSetting("enable_".$reminder_template))
{
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
nlog("Firing reminder email for invoice {$invoice->number}");
});
if ($invoice->invitations->count() > 0) {
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
if ($invoice->invitations->count() > 0) {
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
}
}
$invoice->service()->setReminder()->save();
} else {

View File

@ -111,12 +111,9 @@ class WebhookHandler implements ShouldQueue
try {
$response = $client->post($subscription->target_url, [
RequestOptions::JSON => $data, // or 'json' => [...]
]);
if ($response->getStatusCode() == 410 || $response->getStatusCode() == 200)
$subscription->delete();
$response = $client->post($subscription->target_url, [
RequestOptions::JSON => $data, // or 'json' => [...]
]);
SystemLogger::dispatch(
$response,
@ -127,10 +124,13 @@ class WebhookHandler implements ShouldQueue
$this->company
);
// if ($response->getStatusCode() == 410 || $response->getStatusCode() == 200)
// return true;// $subscription->delete();
}
catch(\Exception $e){
// nlog($e->getMessage());
nlog($e->getMessage());
SystemLogger::dispatch(
$e->getMessage(),
@ -143,8 +143,6 @@ class WebhookHandler implements ShouldQueue
}
}
public function failed($exception)

View File

@ -47,8 +47,6 @@ class InvitationViewedListener implements ShouldQueue
$entity_name = lcfirst(class_basename($event->entity));
$invitation = $event->invitation;
// $notification = new EntityViewedNotification($invitation, $entity_name);
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer( (new EntityViewedObject($invitation, $entity_name))->build() );
$nmo->company = $invitation->company;
@ -57,8 +55,9 @@ class InvitationViewedListener implements ShouldQueue
foreach ($invitation->company->company_users as $company_user) {
$entity_viewed = "{$entity_name}_viewed";
$entity_viewed_all = "{$entity_name}_viewed_all";
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed]);
$methods = $this->findUserNotificationTypes($invitation, $company_user, $entity_name, ['all_notifications', $entity_viewed, $entity_viewed_all]);
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
@ -68,16 +67,7 @@ class InvitationViewedListener implements ShouldQueue
}
// $notification->method = $methods;
// $company_user->user->notify($notification);
}
// if (isset($invitation->company->slack_webhook_url)) {
// $notification->method = ['slack'];
// Notification::route('slack', $invitation->company->slack_webhook_url)
// ->notify($notification);
// }
}
}

View File

@ -16,6 +16,7 @@ use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\VerifyUserObject;
use App\Mail\User\UserAdded;
use App\Notifications\Ninja\VerifyUser;
use App\Utils\Ninja;
use Exception;
@ -52,5 +53,13 @@ class SendVerificationNotification implements ShouldQueue
$event->user->service()->invite($event->company);
$nmo = new NinjaMailerObject;
$nmo->mailable = new UserAdded($event->company, $event->creating_user, $event->user);
$nmo->company = $event->company;
$nmo->settings = $event->company->settings;
$nmo->to_user = $event->creating_user;
NinjaMailerJob::dispatch($nmo);
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail\Company;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CompanyDeleted extends Mailable
{
// use Queueable, SerializesModels;
public $account;
public $company;
public $user;
public $settings;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($company, $user, $account, $settings)
{
$this->company = $company;
$this->user = $user;
$this->account = $account;
$this->settings = $settings;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.company_deleted'))
->view('email.admin.company_deleted')
->with([
'settings' => $this->settings,
'logo' => '',
'title' => ctrans('texts.company_deleted'),
'body' => ctrans('texts.company_deleted_body', ['company' => $this->company, 'user' => $this->user->present()->name(), 'time' => now()]),
'whitelabel' => $this->account->isPaid(),
]);
}
}

View File

@ -62,7 +62,7 @@ class SupportMessageSent extends Mailable
$user = auth()->user();
if(Ninja::isHosted())
$subject = "{$priority}Hosted-{$company->db} :: Customer Support - [{$plan}] ".date('M jS, g:ia');
$subject = "{$priority}Hosted-{$company->db} :: Customer Support - {$plan} ".date('M jS, g:ia');
else
$subject = "{$priority}Self Hosted :: Customer Support - [{$plan}] ".date('M jS, g:ia');

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Mail\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class UserAdded extends Mailable
{
// use Queueable, SerializesModels;
public $company;
public $user;
public $created_user;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($company, $user, $created_user)
{
$this->company = $company;
$this->user = $user;
$this->created_user = $created_user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject(ctrans('texts.created_user'))
->view('email.admin.user_added')
->with([
'settings' => $this->company->settings,
'logo' => $this->company->present()->logo(),
'title' => ctrans('texts.created_user'),
'body' => ctrans('texts.user_created_user', ['user' => $this->user->present()->name(), 'created_user' => $this->created_user->present()->name(), 'time' => now()]),
'whitelabel' => $this->company->account->isPaid(),
]);
}
}

View File

@ -19,6 +19,8 @@ use App\Models\Webhook;
class InvoiceObserver
{
public $afterCommit = true;
/**
* Handle the client "created" event.
*
@ -27,6 +29,7 @@ class InvoiceObserver
*/
public function created(Invoice $invoice)
{
$subscriptions = Webhook::where('company_id', $invoice->company->id)
->where('event_id', Webhook::EVENT_CREATE_INVOICE)
->exists();

View File

@ -283,7 +283,7 @@ class BaseDriver extends AbstractPaymentDriver
$fee_total = $this->payment_hash->fee_total;
/*Hydrate invoices*/
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->withTrashed()->get();
$invoices->each(function ($invoice) use ($fee_total) {
if (collect($invoice->line_items)->contains('type_id', '3')) {
@ -303,7 +303,7 @@ class BaseDriver extends AbstractPaymentDriver
*/
public function unWindGatewayFees(PaymentHash $payment_hash)
{
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->get();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
$invoices->each(function ($invoice) {
$invoice->service()->removeUnpaidGatewayFees();
@ -384,7 +384,7 @@ class BaseDriver extends AbstractPaymentDriver
$nmo->company = $gateway->client->company;
$nmo->settings = $gateway->client->company->settings;
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->get();
$invoices->each(function ($invoice){

View File

@ -271,7 +271,7 @@ class BasePaymentDriver
public function attachInvoices(Payment $payment, PaymentHash $payment_hash): Payment
{
$paid_invoices = $payment_hash->invoices();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->get();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->withTrashed()->get();
$payment->invoices()->sync($invoices);
$payment->save();
@ -300,7 +300,7 @@ class BasePaymentDriver
// $invoice_totals = array_sum(array_column($payment_invoices,'amount'));
/*Hydrate invoices*/
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get();
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->withTrashed()->get();
$invoices->each(function ($invoice) use ($fee_total) {
if (collect($invoice->line_items)->contains('type_id', '3')) {

View File

@ -149,7 +149,7 @@ class BraintreePaymentDriver extends BaseDriver
{
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->first();
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}";

View File

@ -198,7 +198,7 @@ class CheckoutComPaymentDriver extends BaseDriver
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->first();
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->client->present()->name()}";

View File

@ -61,7 +61,7 @@ class CustomPaymentDriver extends BaseDriver
if (count($this->payment_hash->invoices()) > 0) {
$invoice_id = $this->decodePrimaryKey($this->payment_hash->invoices()[0]->invoice_id);
$invoice = Invoice::findOrFail($invoice_id);
$invoice = Invoice::withTrashed()->find($invoice_id);
$variables = (new HtmlEngine($invoice->invitations->first()))->generateLabelsAndValues();
}

View File

@ -248,7 +248,7 @@ class CreditCard
$payment = $this->payfast->createPayment($payment_record, Payment::STATUS_COMPLETED);
return redirect()->route('client.payments.show', ['payment' => $this->payfast->encodePrimaryKey($payment->id)]);
//return redirect()->route('client.payments.show', ['payment' => $this->payfast->encodePrimaryKey($payment->id)]);
}
private function processUnsuccessfulPayment($server_response)

View File

@ -169,6 +169,7 @@ class PayFastPaymentDriver extends BaseDriver
{
$data = $request->all();
nlog("payfast");
nlog($data);
if(array_key_exists('m_payment_id', $data))
@ -179,17 +180,23 @@ class PayFastPaymentDriver extends BaseDriver
switch ($hash)
{
case 'cc_auth':
return $this->setPaymentMethod(GatewayType::CREDIT_CARD)
->authorizeResponse($request);
$this->setPaymentMethod(GatewayType::CREDIT_CARD)
->authorizeResponse($request);
return response()->json([], 200);
break;
default:
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$data['m_payment_id']])->first();
return $this->setPaymentMethod(GatewayType::CREDIT_CARD)
->setPaymentHash($payment_hash)
->processPaymentResponse($request);
$this->setPaymentMethod(GatewayType::CREDIT_CARD)
->setPaymentHash($payment_hash)
->processPaymentResponse($request);
return response()->json([], 200);
break;
}

View File

@ -197,36 +197,23 @@ class PayPalExpressPaymentDriver extends BaseDriver
public function generatePaymentItems(array $data)
{
$total = 0;
$items = collect($this->payment_hash->data->invoices)->map(function ($i) use (&$total) {
$invoice = Invoice::findOrFail($this->decodePrimaryKey($i->invoice_id));
$_invoice = collect($this->payment_hash->data->invoices)->first();
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
return collect($invoice->line_items)->map(function ($lineItem) use (&$total) {
if (floatval($lineItem->quantity) != intval($lineItem->quantity)) {
return null;
}
$line_item = collect($invoice->line_items)->first();
$total += $lineItem->cost * $lineItem->quantity;
$items = [];
return new Item([
'name' => $lineItem->product_key,
'description' => substr(strip_tags($lineItem->notes), 0, 100),
'price' => $lineItem->cost,
'quantity' => $lineItem->quantity,
]);
});
});
if ($total != $data['total']['amount_with_fee']) {
$items[0][] = new Item([
'name' => trans('texts.taxes_and_fees'),
'description' => '',
'price' => $data['total']['amount_with_fee'] - $total,
$items[] = new Item([
'name' => " ",
'description' => ctrans('texts.invoice_number') . "# " . $invoice->number,
'price' => $data['total']['amount_with_fee'],
'quantity' => 1,
]);
}
return $items[0]->toArray();
return $items;
}
}

View File

@ -60,7 +60,8 @@ class ACH
$customer = $this->stripe->findOrCreateCustomer();
try {
$source = $this->stripe->stripe->customers->createSource($customer->id, ['source' => $stripe_response->token->id]);
$source = Customer::createSource($customer->id, ['source' => $stripe_response->token->id], $this->stripe->stripe_connect_auth);
// $source = $this->stripe->stripe->customers->createSource($customer->id, ['source' => $stripe_response->token->id]);
} catch (InvalidRequestException $e) {
throw new PaymentFailed($e->getMessage(), $e->getCode());
}

View File

@ -52,7 +52,7 @@ class Charge
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
$amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total;
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->first();
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
@ -62,7 +62,6 @@ class Charge
$this->stripe->init();
$response = null;
try {
@ -75,7 +74,7 @@ class Charge
'confirm' => true,
'description' => $description,
];
nlog($data);
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);
// $response = $local_stripe->paymentIntents->create($data);

View File

@ -48,91 +48,99 @@ class UpdateReminder extends AbstractService
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_invoice_date' &&
$this->settings->num_days_reminder1 > 0) {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'before_due_date' &&
$this->settings->num_days_reminder1 > 0) {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder1_sent) &&
$this->settings->schedule_reminder1 == 'after_due_date' &&
$this->settings->num_days_reminder1 > 0) {
$this->settings->enable_reminder1) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder1)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_invoice_date' &&
$this->settings->num_days_reminder2 > 0) {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'before_due_date' &&
$this->settings->num_days_reminder2 > 0) {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder2_sent) &&
$this->settings->schedule_reminder2 == 'after_due_date' &&
$this->settings->num_days_reminder2 > 0) {
$this->settings->enable_reminder2) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder2)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_invoice_date' &&
$this->settings->num_days_reminder3 > 0) {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'before_due_date' &&
$this->settings->num_days_reminder3 > 0) {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->subDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if (is_null($this->invoice->reminder3_sent) &&
$this->settings->schedule_reminder3 == 'after_due_date' &&
$this->settings->num_days_reminder3 > 0) {
$this->settings->enable_reminder3) {
$reminder_date = Carbon::parse($this->invoice->due_date)->startOfDay()->addDays($this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if ($this->invoice->last_sent_date &&
(int)$this->settings->endless_reminder_frequency_id > 0) {
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->endless_reminder_frequency_id)->addSeconds($offset);
$this->settings->enable_reminder_endless) {
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->endless_reminder_frequency_id);
if($reminder_date){
$reminder_date->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)))
$date_collection->push($reminder_date);
}
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date);
}
if($date_collection->count() >=1 && $date_collection->sort()->first()->gte(now()))
@ -143,6 +151,20 @@ class UpdateReminder extends AbstractService
return $this->invoice;
}
private function testReminderValid($reminder_number, $reminder_schedule) :bool
{
$reminder_sent = "reminder{$reminder_number}_sent";
$schedule_reminder = "schedule_reminder{$reminder_number}";
$enable_reminder = "enable_reminder{$reminder_number}";
$late_fee_amount = "late_fee_amount{$reminder_number}";
$late_fee_percent = "late_fee_percent{$reminder_number}";
return (is_null($this->invoice->{$reminder_sent}) &&
$this->settings->{$schedule_reminder} == $reminder_schedule &&
($this->settings->{$enable_reminder} || $late_fee_percent > 0 || $late_fee_amount > 0));
}
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{

View File

@ -51,6 +51,7 @@ class DesignTransformer extends EntityTransformer
'archived_at' => (int) $design->deleted_at,
'created_at' => (int) $design->created_at,
'is_deleted' => (bool) $design->is_deleted,
'is_free' => ($design->id <= 4) ? true : false,
];
}
}

View File

@ -167,6 +167,7 @@ class HtmlEngine
$data['$invoice.discount'] = ['value' => Number::formatMoney($this->entity_calc->getTotalDiscount(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.discount')];
$data['$discount'] = &$data['$invoice.discount'];
$data['$subtotal'] = ['value' => Number::formatMoney($this->entity_calc->getSubTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
$data['$net_subtotal'] = ['value' => Number::formatMoney(($this->entity_calc->getSubTotal() - $this->entity->total_taxes), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.subtotal')];
$data['$invoice.subtotal'] = &$data['$subtotal'];
if ($this->entity->partial > 0) {

View File

@ -170,4 +170,29 @@ class Ninja
// return implode('-', $parts);
// }
//
/*
* Available - but not recommended for use
*
* This will guarantee a given string IS the correct format for a
* base64 encoded string ,
* but can't guarantee that it is a base64 encoded string
*
*/
public static function isBase64Encoded(string $s) : bool
{
// Check if there are valid base64 characters
if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $s)) return false;
// Decode the string in strict mode and check the results
$decoded = base64_decode($s, true);
if(false === $decoded) return false;
// if string returned contains not printable chars
if (0 < preg_match('/((?![[:graph:]])(?!\s)(?!\p{L}))./', $decoded, $matched)) return false;
// Encode the string again
if(base64_encode($decoded) != $s) return false;
return true;
}
}

View File

@ -83,6 +83,7 @@ class SystemHealth
'flutter_renderer' => (string)config('ninja.flutter_canvas_kit'),
'jobs_pending' => (int) Queue::size(),
'pdf_engine' => (string) self::getPdfEngine(),
'queue' => (string) config('queue.default'),
];
}

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.2.13',
'app_tag' => '5.2.13',
'app_version' => '5.2.14',
'app_tag' => '5.2.14',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SetInvoiceTaskDatelogTrueInCompaniesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('companies', function (Blueprint $table) {
$table->boolean('invoice_task_datelog')->default(1)->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
}
}

12188
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ const TEMP = 'flutter-temp-cache';
const CACHE_NAME = 'flutter-app-cache';
const RESOURCES = {
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"main.dart.js": "fea9b4fc231a933ebcf3f80aa8c9eb45",
"main.dart.js": "0e2c2fce69f7c2311615c518afa55e3e",
"/": "23224b5e03519aaa87594403d54412cf",
"manifest.json": "ce1b79950eb917ea619a0a30da27c6a3",
"assets/packages/material_design_icons_flutter/lib/fonts/materialdesignicons-webfont.ttf": "174c02fc4609e8fc4389f5d21f16a296",

4
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4281,6 +4281,9 @@ $LANG = array(
'quotes_with_status_sent_can_be_approved' => 'Only quotes with "Sent" status can be approved.',
'no_quotes_available_for_download' => 'No quotes available for download.',
'copyright' => 'Copyright',
'user_created_user' => ':user created :created_user at :time',
'company_deleted' => 'Company deleted',
'company_deleted_body' => 'Company [ :company ] was deleted by :user',
);
return $LANG;

View File

@ -0,0 +1,6 @@
@component('email.template.admin', ['logo' => $logo, 'settings' => $settings])
<div class="center">
<h1>{!! $title !!}</h1>
<p>{!! $body !!}</p>
</div>
@endcomponent

View File

@ -0,0 +1,6 @@
@component('email.template.admin', ['logo' => $logo, 'settings' => $settings])
<div class="center">
<h1>{!! $title !!}</h1>
<p>{!! $body !!}</p>
</div>
@endcomponent

View File

@ -12,6 +12,8 @@
namespace Tests\Feature\ClientPortal;
use App\DataMapper\ClientSettings;
use App\DataMapper\CompanySettings;
use App\Http\Livewire\CreditsTable;
use App\Models\Account;
use App\Models\Client;
@ -19,6 +21,7 @@ use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Credit;
use App\Models\User;
use App\Utils\Traits\AppSetup;
use Faker\Factory;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire;
@ -28,12 +31,14 @@ use function now;
class CreditsTest extends TestCase
{
use DatabaseTransactions;
use AppSetup;
public function setUp(): void
{
parent::setUp();
$this->faker = Factory::create();
$this->buildCache(true);
}
public function testShowingOnlyCreditsWithDueDateLessOrEqualToNow()
@ -45,8 +50,12 @@ class CreditsTest extends TestCase
);
$company = Company::factory()->create(['account_id' => $account->id]);
$company->settings = CompanySettings::defaults();
$company->save();
$client = Client::factory()->create(['company_id' => $company->id, 'user_id' => $user->id]);
$client->settings = ClientSettings::defaults();
$client->save();
ClientContact::factory()->count(2)->create([
'user_id' => $user->id,

View File

@ -20,6 +20,7 @@ use App\Models\ClientContact;
use App\Models\Company;
use App\Models\Invoice;
use App\Models\User;
use App\Utils\Traits\AppSetup;
use Faker\Factory;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire;
@ -28,12 +29,15 @@ use Tests\TestCase;
class InvoicesTest extends TestCase
{
use DatabaseTransactions;
use AppSetup;
public function setUp(): void
{
parent::setUp();
$this->faker = Factory::create();
$this->buildCache(true);
}
public function testInvoiceTableFilters()

View File

@ -86,9 +86,13 @@ class ReminderTest extends TestCase
{
$this->invoice->date = now()->subDays(2)->format('Y-m-d');
$this->invoice->due_date = Carbon::now()->addDays(30)->format('Y-m-d');
$this->invoice->due_date = now()->addDays(30)->format('Y-m-d');
$this->invoice->reminder1_sent = now()->subDays(1)->format('Y-m-d');
$this->invoice->last_sent_date = now()->subDays(1)->format('Y-m-d');
$this->invoice->next_send_date = now()->subDays(1)->format('Y-m-d');
$this->invoice->reminder2_sent = null;
$settings = $this->company->settings;
$settings->enable_reminder1 = true;
$settings->schedule_reminder1 = 'after_invoice_date';

57
tests/Unit/Base64Test.php Normal file
View File

@ -0,0 +1,57 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Unit;
use App\Utils\Ninja;
use Tests\TestCase;
/**
* @test
*/
class Base64Test extends TestCase
{
/**
* Important consideration with Base64
* encoding checks.
*
* No method can guarantee against false positives.
*/
public function setUp() :void
{
parent::setUp();
}
public function testBadBase64String()
{
$this->assertFalse(Ninja::isBase64Encoded('x'));
}
public function testCorrectBase64Encoding()
{
$this->assertTrue(Ninja::isBase64Encoded('MTIzNDU2'));
}
public function testBadBase64StringScenaro1()
{
$this->assertFalse(Ninja::isBase64Encoded('Matthies'));
}
public function testBadBase64StringScenaro2()
{
$this->assertFalse(Ninja::isBase64Encoded('Barthels'));
}
public function testBadBase64StringScenaro3()
{
$this->assertFalse(Ninja::isBase64Encoded('aaa'));
}
}