Merge pull request #7914 from turbo124/v5-stable

v5.5.37
This commit is contained in:
David Bomba 2022-11-02 14:19:42 +11:00 committed by GitHub
commit 4b0935185f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 5307 additions and 5107 deletions

View File

@ -1 +1 @@
5.5.36 5.5.37

View File

@ -198,7 +198,7 @@ class Handler extends ExceptionHandler
// nlog($exception->validator->getMessageBag()); // nlog($exception->validator->getMessageBag());
return response()->json(['message' => 'The given data was invalid.', 'errors' => $exception->validator->getMessageBag()], 422); return response()->json(['message' => 'The given data was invalid.', 'errors' => $exception->validator->getMessageBag()], 422);
} elseif ($exception instanceof RelationNotFoundException && $request->expectsJson()) { } elseif ($exception instanceof RelationNotFoundException && $request->expectsJson()) {
return response()->json(['message' => $exception->getMessage()], 400); return response()->json(['message' => "Relation `{$exception->relation}` is not a valid include."], 400);
} elseif ($exception instanceof GenericPaymentDriverFailure && $request->expectsJson()) { } elseif ($exception instanceof GenericPaymentDriverFailure && $request->expectsJson()) {
return response()->json(['message' => $exception->getMessage()], 400); return response()->json(['message' => $exception->getMessage()], 400);
} elseif ($exception instanceof GenericPaymentDriverFailure) { } elseif ($exception instanceof GenericPaymentDriverFailure) {

View File

@ -169,7 +169,7 @@ abstract class QueryFilters
public function clientFilter() public function clientFilter()
{ {
if (auth()->guard('contact')->user()) { if (auth()->guard('contact')->user()) {
return $this->builder->whereClientId(auth()->guard('contact')->user()->client->id); return $this->builder->where('client_id', auth()->guard('contact')->user()->client->id);
} }
} }
@ -179,6 +179,15 @@ abstract class QueryFilters
$created_at = date('Y-m-d H:i:s', $value); $created_at = date('Y-m-d H:i:s', $value);
if(is_string($created_at)){
$created_at = strtotime(str_replace("/","-",$created_at));
if(!$created_at)
return $this->builder;
}
return $this->builder->where('created_at', '>=', $created_at); return $this->builder->where('created_at', '>=', $created_at);
} }

View File

@ -40,4 +40,7 @@ function nlog($output, $context = []): void
} else { } else {
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context); \Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);
} }
$output = null;
$context = null;
} }

View File

@ -105,14 +105,14 @@ class SwissQrGenerator
// Add payment reference // Add payment reference
// This is what you will need to identify incoming payments. // This is what you will need to identify incoming payments.
if(stripos($this->invoice->number, "Live-") === 0) if(stripos($this->invoice->number, "Live") === 0)
{ {
// we're currently in preview status. Let's give a dummy reference for now // we're currently in preview status. Let's give a dummy reference for now
$invoice_number = "123456789"; $invoice_number = "123456789";
} }
else else
{ {
$invoice_number = $this->invoice->number; $invoice_number = iconv("UTF-8", "ASCII", $this->invoice->number);
} }
if(strlen($this->company->present()->besr_id()) > 1) if(strlen($this->company->present()->besr_id()) > 1)
@ -141,7 +141,7 @@ class SwissQrGenerator
// Optionally, add some human-readable information about what the bill is for. // Optionally, add some human-readable information about what the bill is for.
$qrBill->setAdditionalInformation( $qrBill->setAdditionalInformation(
QrBill\DataGroup\Element\AdditionalInformation::create( QrBill\DataGroup\Element\AdditionalInformation::create(
$this->invoice->public_notes ? substr($this->invoice->public_notes, 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $invoice_number]) $this->invoice->public_notes ? substr($this->invoice->public_notes, 0, 139) : ctrans('texts.invoice_number_placeholder', ['invoice' => $this->invoice->number])
) )
); );

View File

@ -33,6 +33,7 @@ use App\Models\Client;
use App\Models\Credit; use App\Models\Credit;
use App\Models\Invoice; use App\Models\Invoice;
use App\Repositories\CreditRepository; use App\Repositories\CreditRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\CreditTransformer; use App\Transformers\CreditTransformer;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\TempFile; use App\Utils\TempFile;
@ -534,6 +535,20 @@ class CreditController extends BaseController
return response()->json(['message' => ctrans('texts.sent_message')], 200); return response()->json(['message' => ctrans('texts.sent_message')], 200);
} }
if($action == 'bulk_print' && auth()->user()->can('view', $credits->first())){
$paths = $credits->map(function ($credit){
return $credit->service()->getCreditPdf($credit->invitations->first());
});
$merge = (new PdfMerge($paths->toArray()))->run();
return response()->streamDownload(function () use ($merge) {
echo ($merge);
}, 'print.pdf', ['Content-Type' => 'application/pdf']);
}
$credits->each(function ($credit, $key) use ($action) { $credits->each(function ($credit, $key) use ($action) {
if (auth()->user()->can('edit', $credit)) { if (auth()->user()->can('edit', $credit)) {
$this->performAction($credit, $action, true); $this->performAction($credit, $action, true);

View File

@ -40,6 +40,7 @@ use App\Models\Invoice;
use App\Models\Quote; use App\Models\Quote;
use App\Models\TransactionEvent; use App\Models\TransactionEvent;
use App\Repositories\InvoiceRepository; use App\Repositories\InvoiceRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\InvoiceTransformer; use App\Transformers\InvoiceTransformer;
use App\Transformers\QuoteTransformer; use App\Transformers\QuoteTransformer;
use App\Utils\Ninja; use App\Utils\Ninja;
@ -588,6 +589,20 @@ class InvoiceController extends BaseController
} }
if($action == 'bulk_print' && auth()->user()->can('view', $invoices->first())){
$paths = $invoices->map(function ($invoice){
return $invoice->service()->getInvoicePdf();
});
$merge = (new PdfMerge($paths->toArray()))->run();
return response()->streamDownload(function () use ($merge) {
echo ($merge);
}, 'print.pdf', ['Content-Type' => 'application/pdf']);
}
/* /*
* Send the other actions to the switch * Send the other actions to the switch
*/ */

View File

@ -32,6 +32,7 @@ use App\Models\Client;
use App\Models\Expense; use App\Models\Expense;
use App\Models\PurchaseOrder; use App\Models\PurchaseOrder;
use App\Repositories\PurchaseOrderRepository; use App\Repositories\PurchaseOrderRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\ExpenseTransformer; use App\Transformers\ExpenseTransformer;
use App\Transformers\PurchaseOrderTransformer; use App\Transformers\PurchaseOrderTransformer;
use App\Utils\Ninja; use App\Utils\Ninja;
@ -515,6 +516,20 @@ class PurchaseOrderController extends BaseController
return response()->json(['message' => ctrans('texts.sent_message')], 200); return response()->json(['message' => ctrans('texts.sent_message')], 200);
} }
if($action == 'bulk_print' && auth()->user()->can('view', $purchase_orders->first())){
$paths = $purchase_orders->map(function ($purchase_order){
return $purchase_order->service()->getPurchaseOrderPdf();
});
$merge = (new PdfMerge($paths->toArray()))->run();
return response()->streamDownload(function () use ($merge) {
echo ($merge);
}, 'print.pdf', ['Content-Type' => 'application/pdf']);
}
/* /*
* Send the other actions to the switch * Send the other actions to the switch
*/ */

View File

@ -35,6 +35,7 @@ use App\Models\Invoice;
use App\Models\Project; use App\Models\Project;
use App\Models\Quote; use App\Models\Quote;
use App\Repositories\QuoteRepository; use App\Repositories\QuoteRepository;
use App\Services\PdfMaker\PdfMerge;
use App\Transformers\InvoiceTransformer; use App\Transformers\InvoiceTransformer;
use App\Transformers\ProjectTransformer; use App\Transformers\ProjectTransformer;
use App\Transformers\QuoteTransformer; use App\Transformers\QuoteTransformer;
@ -561,6 +562,20 @@ class QuoteController extends BaseController
return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()); return $this->listResponse(Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
} }
if($action == 'bulk_print' && auth()->user()->can('view', $quotes->first())){
$paths = $quotes->map(function ($quote){
return $quote->service()->getQuotePdf();
});
$merge = (new PdfMerge($paths->toArray()))->run();
return response()->streamDownload(function () use ($merge) {
echo ($merge);
}, 'print.pdf', ['Content-Type' => 'application/pdf']);
}
if($action == 'convert_to_project') if($action == 'convert_to_project')
{ {

View File

@ -112,6 +112,8 @@ class SelfUpdateController extends BaseController
$zipFile->close(); $zipFile->close();
$zipFile = null;
nlog('Finished extracting files'); nlog('Finished extracting files');
unlink($file); unlink($file);

View File

@ -55,7 +55,7 @@ class ValidRefundableRequest implements Rule
} }
$request_invoices = request()->has('invoices') ? $this->input['invoices'] : []; $request_invoices = request()->has('invoices') ? $this->input['invoices'] : [];
$request_credits = request()->has('credits') ? $this->input['credits'] : []; // $request_credits = request()->has('credits') ? $this->input['credits'] : [];
if ($payment->invoices()->exists()) { if ($payment->invoices()->exists()) {
foreach ($payment->invoices as $paymentable_invoice) { foreach ($payment->invoices as $paymentable_invoice) {

View File

@ -44,6 +44,30 @@ class BaseTransformer
$this->company = $company; $this->company = $company;
} }
public function parseDate($date)
{
try{
$parsed_date = Carbon::parse($date);
return $parsed_date->format('Y-m-d');
}
catch(\Exception $e){
$parsed_date = date('Y-m-d', strtotime($date));
if($parsed_date == '1970-01-01')
return now()->format('Y-m-d');
return $parsed_date;
}
}
public function getString($data, $field) public function getString($data, $field)
{ {
return isset($data[$field]) && $data[$field] ? trim($data[$field]) : ''; return isset($data[$field]) && $data[$field] ? trim($data[$field]) : '';

View File

@ -31,7 +31,7 @@ class ExpenseTransformer extends BaseTransformer
return [ return [
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'amount' => $this->getFloat($data, 'expense.amount'), 'amount' => abs($this->getFloat($data, 'expense.amount')),
'currency_id' => $this->getCurrencyByCode( 'currency_id' => $this->getCurrencyByCode(
$data, $data,
'expense.currency_id' 'expense.currency_id'

View File

@ -57,10 +57,10 @@ class InvoiceTransformer extends BaseTransformer
'discount' => $this->getFloat($invoice_data, 'invoice.discount'), 'discount' => $this->getFloat($invoice_data, 'invoice.discount'),
'po_number' => $this->getString($invoice_data, 'invoice.po_number'), 'po_number' => $this->getString($invoice_data, 'invoice.po_number'),
'date' => isset($invoice_data['invoice.date']) 'date' => isset($invoice_data['invoice.date'])
? date('Y-m-d', strtotime(str_replace("/","-",$invoice_data['invoice.date']))) ? $this->parseDate($invoice_data['invoice.date'])
: now()->format('Y-m-d'), : now()->format('Y-m-d'),
'due_date' => isset($invoice_data['invoice.due_date']) 'due_date' => isset($invoice_data['invoice.due_date'])
? date('Y-m-d', strtotime(str_replace("/","-",$invoice_data['invoice.due_date']))) ? $this->parseDate($invoice_data['invoice.due_date'])
: null, : null,
'terms' => $this->getString($invoice_data, 'invoice.terms'), 'terms' => $this->getString($invoice_data, 'invoice.terms'),
'public_notes' => $this->getString( 'public_notes' => $this->getString(
@ -140,10 +140,7 @@ class InvoiceTransformer extends BaseTransformer
$transformed['payments'] = [ $transformed['payments'] = [
[ [
'date' => isset($invoice_data['payment.date']) 'date' => isset($invoice_data['payment.date'])
? date( ? $this->parseDate($invoice_data['payment.date'])
'Y-m-d',
strtotime($invoice_data['payment.date'])
)
: date('y-m-d'), : date('y-m-d'),
'transaction_reference' => $this->getString( 'transaction_reference' => $this->getString(
$invoice_data, $invoice_data,
@ -159,10 +156,7 @@ class InvoiceTransformer extends BaseTransformer
$transformed['payments'] = [ $transformed['payments'] = [
[ [
'date' => isset($invoice_data['payment.date']) 'date' => isset($invoice_data['payment.date'])
? date( ? $this->parseDate($invoice_data['payment.date'])
'Y-m-d',
strtotime($invoice_data['payment.date'])
)
: date('y-m-d'), : date('y-m-d'),
'transaction_reference' => $this->getString( 'transaction_reference' => $this->getString(
$invoice_data, $invoice_data,
@ -182,10 +176,7 @@ class InvoiceTransformer extends BaseTransformer
$transformed['payments'] = [ $transformed['payments'] = [
[ [
'date' => isset($invoice_data['payment.date']) 'date' => isset($invoice_data['payment.date'])
? date( ? $this->parseDate($invoice_data['payment.date'])
'Y-m-d',
strtotime($invoice_data['payment.date'])
)
: date('y-m-d'), : date('y-m-d'),
'transaction_reference' => $this->getString( 'transaction_reference' => $this->getString(
$invoice_data, $invoice_data,

View File

@ -219,6 +219,14 @@ class CreateEntityPdf implements ShouldQueue
} }
} }
$this->invitation = null;
$this->entity = null;
$this->company = null;
$this->client = null;
$this->contact = null;
$maker = null;
$state = null;
return $file_path; return $file_path;
} }

View File

@ -200,6 +200,8 @@ class CreateRawPdf implements ShouldQueue
} }
if ($pdf) { if ($pdf) {
$maker =null;
$state = null;
return $pdf; return $pdf;
} }

View File

@ -128,6 +128,17 @@ class EmailEntity implements ShouldQueue
$nmo->entity = $this->entity; $nmo->entity = $this->entity;
(new NinjaMailerJob($nmo))->handle(); (new NinjaMailerJob($nmo))->handle();
$nmo = null;
$this->invitation = null;
$this->company = null;
$this->entity_string = null;
$this->entity = null;
$this->settings = null;
$this->reminder_template = null;
$this->html_engine = null;
$this->template_data = null;
$this->email_entity_builder = null;
} }
private function resolveEntityString() :string private function resolveEntityString() :string

View File

@ -23,6 +23,7 @@ use App\Libraries\MultiDB;
use App\Models\Client; use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use App\Models\Vendor; use App\Models\Vendor;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@ -81,6 +82,10 @@ class CSVIngest implements ShouldQueue
$engine->finalizeImport(); $engine->finalizeImport();
$this->checkContacts(); $this->checkContacts();
if(Ninja::isHosted())
app('queue.worker')->shouldQuit = 1;
} }
private function checkContacts() private function checkContacts()

View File

@ -115,6 +115,7 @@ class NinjaMailerJob implements ShouldQueue
//send email //send email
try { try {
nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString()); nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
nlog("Using mailer => ". $this->mailer); nlog("Using mailer => ". $this->mailer);
@ -128,6 +129,14 @@ class NinjaMailerJob implements ShouldQueue
LightLogs::create(new EmailSuccess($this->nmo->company->company_key)) LightLogs::create(new EmailSuccess($this->nmo->company->company_key))
->send(); ->send();
// nlog('Using ' . ((int) (memory_get_usage(true) / (1024 * 1024))) . 'MB ');
$this->nmo = null;
$this->company = null;
$mem_usage = memory_get_usage();
nlog('The script is now using: ' . round($mem_usage / 1024) . 'KBof memory.');
} catch (\Exception | \RuntimeException | \Google\Service\Exception $e) { } catch (\Exception | \RuntimeException | \Google\Service\Exception $e) {
@ -166,7 +175,16 @@ class NinjaMailerJob implements ShouldQueue
/* Don't send postmark failures to Sentry */ /* Don't send postmark failures to Sentry */
if(Ninja::isHosted() && (!$e instanceof ClientException)) if(Ninja::isHosted() && (!$e instanceof ClientException))
app('sentry')->captureException($e); app('sentry')->captureException($e);
$message = null;
$this->nmo = null;
$this->company = null;
app('queue.worker')->shouldQuit = 1;
} }
} }
/* Switch statement to handle failure notifications */ /* Switch statement to handle failure notifications */
@ -188,6 +206,7 @@ class NinjaMailerJob implements ShouldQueue
if ($this->nmo->to_user instanceof ClientContact) if ($this->nmo->to_user instanceof ClientContact)
$this->logMailError($message, $this->nmo->to_user->client); $this->logMailError($message, $this->nmo->to_user->client);
} }
private function setMailDriver() private function setMailDriver()
@ -381,7 +400,7 @@ class NinjaMailerJob implements ShouldQueue
private function logMailError($errors, $recipient_object) private function logMailError($errors, $recipient_object)
{ {
SystemLogger::dispatch( SystemLogger::dispatchSync(
$errors, $errors,
SystemLog::CATEGORY_MAIL, SystemLog::CATEGORY_MAIL,
SystemLog::EVENT_MAIL_SEND, SystemLog::EVENT_MAIL_SEND,
@ -396,6 +415,9 @@ class NinjaMailerJob implements ShouldQueue
LightLogs::create($job_failure) LightLogs::create($job_failure)
->send(); ->send();
$job_failure = null;
} }
public function failed($exception = null) public function failed($exception = null)

View File

@ -99,7 +99,7 @@ class ReminderJob implements ShouldQueue
(Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) {
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
EmailEntity::dispatchSync($invitation, $invitation->company, $reminder_template); EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
}); });

View File

@ -79,11 +79,22 @@ class SystemLogger implements ShouldQueue
if (! $this->log) { if (! $this->log) {
nlog('SystemLogger:: no log to store'); nlog('SystemLogger:: no log to store');
$this->category_id = null;
$this->event_id = null;
$this->type_id = null;
$this->client = null;
$this->company = null;
return; return;
} }
SystemLog::create($sl); SystemLog::create($sl);
$this->log = null;
$this->category_id = null;
$this->event_id = null;
$this->type_id = null;
$this->client = null;
$this->company = null;
} }
public function failed($e) public function failed($e)

View File

@ -213,6 +213,9 @@ class CreatePurchaseOrderPdf implements ShouldQueue
info($maker->getCompiledHTML()); info($maker->getCompiledHTML());
} }
$maker = null;
$state = null;
return $pdf; return $pdf;
} }

View File

@ -515,17 +515,10 @@ class MultiDB
{ {
/* This will set the database connection for the request */ /* This will set the database connection for the request */
config(['database.default' => $database]); config(['database.default' => $database]);
// for some reason this breaks everything _hard_
// DB::purge($database);
// DB::reconnect($database);
} }
public static function setDefaultDatabase() public static function setDefaultDatabase()
{ {
config(['database.default' => config('ninja.db.default')]); config(['database.default' => config('ninja.db.default')]);
// DB::purge(config('ninja.db.default'));
// DB::reconnect(config('ninja.db.default'));
} }
} }

View File

@ -251,17 +251,17 @@ class PaymentEmailEngine extends BaseEmailEngine
private function formatInvoiceField($field) private function formatInvoiceField($field)
{ {
$invoice = ''; $invoicex = '';
foreach ($this->payment->invoices as $invoice) { foreach ($this->payment->invoices as $invoice) {
$invoice_field = $invoice->{$field}; $invoice_field = $invoice->{$field};
$invoice .= ctrans('texts.invoice_number_short') . "{$invoice->number} {$invoice_field}"; $invoicex .= ctrans('texts.invoice_number_short') . "{$invoice->number} {$invoice_field}";
} }
return $invoice; return $invoicex;
} }

View File

@ -95,7 +95,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
return $response->redirect(); return $response->redirect();
} }
$this->sendFailureMail($response->getMessage() ?: ''); // $this->sendFailureMail($response->getMessage() ?: '');
$message = [ $message = [
'server_response' => $response->getMessage(), 'server_response' => $response->getMessage(),

View File

@ -137,6 +137,9 @@ class Charge
return false; return false;
} }
if($response?->status != 'succeeded')
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
if ($cgt->gateway_type_id == GatewayType::SEPA) { if ($cgt->gateway_type_id == GatewayType::SEPA) {
$payment_method_type = PaymentType::SEPA; $payment_method_type = PaymentType::SEPA;
$status = Payment::STATUS_PENDING; $status = Payment::STATUS_PENDING;

View File

@ -41,7 +41,7 @@ class ACH
public function authorizeView($data) public function authorizeView($data)
{ {
$data['gateway'] = $this->wepay_payment_driver; $data['gateway'] = $this->wepay_payment_driver;
$data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2; $data['country_code'] = $this->wepay_payment_driver?->client?->country ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
return render('gateways.wepay.authorize.bank_transfer', $data); return render('gateways.wepay.authorize.bank_transfer', $data);
} }

View File

@ -37,7 +37,7 @@ class CreditCard
public function authorizeView($data) public function authorizeView($data)
{ {
$data['gateway'] = $this->wepay_payment_driver; $data['gateway'] = $this->wepay_payment_driver;
$data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2; $data['country_code'] = $this->wepay_payment_driver?->client?->country ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
return render('gateways.wepay.authorize.authorize', $data); return render('gateways.wepay.authorize.authorize', $data);
} }
@ -102,7 +102,7 @@ class CreditCard
{ {
$data['gateway'] = $this->wepay_payment_driver; $data['gateway'] = $this->wepay_payment_driver;
$data['description'] = ctrans('texts.invoices').': '.collect($data['invoices'])->pluck('invoice_number'); $data['description'] = ctrans('texts.invoices').': '.collect($data['invoices'])->pluck('invoice_number');
$data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2; $data['country_code'] = $this->wepay_payment_driver?->client?->country ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
return render('gateways.wepay.credit_card.pay', $data); return render('gateways.wepay.credit_card.pay', $data);
} }

View File

@ -60,7 +60,7 @@ class ActivityRepository extends BaseRepository
$activity->save(); $activity->save();
//rate limiter //rate limiter
// $this->createBackup($entity, $activity); $this->createBackup($entity, $activity);
} }
/** /**
@ -167,8 +167,13 @@ class ActivityRepository extends BaseRepository
$maker = new PdfMakerService($state); $maker = new PdfMakerService($state);
return $maker->design($template) $html = $maker->design($template)
->build() ->build()
->getCompiledHTML(true); ->getCompiledHTML(true);
$maker = null;
$state = null;
return $html;
} }
} }

View File

@ -304,8 +304,9 @@ class BaseRepository
if (! $model->design_id) if (! $model->design_id)
$model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id')); $model->design_id = $this->decodePrimaryKey($client->getSetting('invoice_design_id'));
//links tasks and expenses back to the invoice. //links tasks and expenses back to the invoice, but only if we are not in the middle of a transaction.
$model->service()->linkEntities()->save(); if (\DB::transactionLevel() == 0)
$model->service()->linkEntities()->save();
if($this->new_model) if($this->new_model)
event('eloquent.created: App\Models\Invoice', $model); event('eloquent.created: App\Models\Invoice', $model);

View File

@ -108,6 +108,9 @@ class Statement
\DB::connection(config('database.default'))->rollBack(); \DB::connection(config('database.default'))->rollBack();
} }
$maker = null;
$state = null;
return $pdf; return $pdf;
} }

View File

@ -107,6 +107,9 @@ class GenerateDeliveryNote
Storage::disk($this->disk)->put($file_path, $pdf); Storage::disk($this->disk)->put($file_path, $pdf);
$maker = null;
$state = null;
return $file_path; return $file_path;
} }
} }

View File

@ -712,6 +712,8 @@ class Design extends BaseDesign
$elements[] = $element; $elements[] = $element;
} }
$document = null;
return $elements; return $elements;
} }

View File

@ -234,44 +234,11 @@ trait DesignHelpers
}); });
"; ";
// Unminified version, just for the reference.
// By default all table headers are hidden with HTML `hidden` property.
// This will check for table data values & if they're not empty it will remove hidden from the column itself.
/*
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(e => {
if ("" !== e.innerText) {
let t = e.getAttribute("data-ref").slice(0, -3);
document.querySelector(`th[data-ref="${t}-th"]`).removeAttribute("hidden");
}
});
document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(e => {
let t = e.getAttribute("data-ref").slice(0, -3);
t = document.querySelector(`th[data-ref="${t}-th"]`);
if (!t.hasAttribute('hidden')) {
return;
}
if ("" == e.innerText) {
e.setAttribute('hidden', 'true');
}
});
}, false);
*/
$javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);';
// Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires, // Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires,
// strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript. // strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript.
/**
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll(`[data-state="encoded-html"]`).forEach((element) => element.innerHTML = element.innerText)
}, false);
*/
$html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);'; $html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);';
return ['element' => 'div', 'elements' => [ return ['element' => 'div', 'elements' => [
@ -391,17 +358,6 @@ document.addEventListener('DOMContentLoaded', function() {
return $converter->convert($markdown); return $converter->convert($markdown);
} }
// public function processMarkdownOnLineItems(array &$items): void
// {
// foreach ($items as $key => $item) {
// foreach ($item as $variable => $value) {
// $item[$variable] = DesignHelpers::parseMarkdownToHtml($value ?? '');
// }
// $items[$key] = $item;
// }
// }
public function processNewLines(array &$items): void public function processNewLines(array &$items): void
{ {
foreach ($items as $key => $item) { foreach ($items as $key => $item) {

View File

@ -0,0 +1,42 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\PdfMaker;
use Illuminate\Support\Facades\Storage;
use \setasign\Fpdi\Fpdi;
use setasign\Fpdi\PdfParser\StreamReader;
class PdfMerge
{
public function __construct(private array $file_paths) {}
public function run()
{
$pdf = new FPDI();
foreach ($this->file_paths as $file) {
$pageCount = $pdf->setSourceFile(StreamReader::createByString(Storage::get($file)));
for ($i = 0; $i < $pageCount; $i++) {
$tpl = $pdf->importPage($i + 1, '/MediaBox');
$pdf->addPage();
$pdf->useTemplate($tpl);
}
}
return $pdf->Output('S');
}
}

View File

@ -143,6 +143,7 @@ class HtmlEngine
$data['$credit.datetime'] = &$data['$entity.datetime']; $data['$credit.datetime'] = &$data['$entity.datetime'];
$data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')]; $data['$payment_button'] = ['value' => '<a class="button" href="'.$this->invitation->getPaymentLink().'">'.ctrans('texts.pay_now').'</a>', 'label' => ctrans('texts.pay_now')];
$data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')]; $data['$payment_link'] = ['value' => $this->invitation->getPaymentLink(), 'label' => ctrans('texts.pay_now')];
$data['$payment_qrcode'] = ['value' => $this->invitation->getPaymentQrCode(), 'label' => ctrans('texts.pay_now')];
if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') { if ($this->entity_string == 'invoice' || $this->entity_string == 'recurring_invoice') {
@ -894,6 +895,10 @@ html {
$dom->appendChild($container); $dom->appendChild($container);
return $dom->saveHTML(); $html = $dom->saveHTML();
$dom = null;
return $html;
} }
} }

View File

@ -137,6 +137,8 @@ class Number
public static function formatMoney($value, $entity) :string public static function formatMoney($value, $entity) :string
{ {
$value = floatval($value);
$currency = $entity->currency(); $currency = $entity->currency();
$thousand = $currency->thousand_separator; $thousand = $currency->thousand_separator;

View File

@ -90,6 +90,9 @@ class SystemHealth
private static function checkCurrencySanity() private static function checkCurrencySanity()
{ {
if(!self::simpleDbCheck())
return true;
if(strlen(config('ninja.currency_converter_api_key')) == 0){ if(strlen(config('ninja.currency_converter_api_key')) == 0){
$cs = DB::table('clients') $cs = DB::table('clients')

View File

@ -13,6 +13,11 @@ namespace App\Utils\Traits;
use App\Utils\Ninja; use App\Utils\Ninja;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use BaconQrCode\Renderer\ImageRenderer;
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
use BaconQrCode\Writer;
/** /**
* Class Inviteable. * Class Inviteable.
@ -54,6 +59,22 @@ trait Inviteable
return $domain.'/client/pay/'.$this->key; return $domain.'/client/pay/'.$this->key;
} }
public function getPaymentQrCode()
{
$renderer = new ImageRenderer(
new RendererStyle(200),
new SvgImageBackEnd()
);
$writer = new Writer($renderer);
$qr = $writer->writeString($this->getPaymentLink());
return "<svg viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
<rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>";
}
public function getUnsubscribeLink() public function getUnsubscribeLink()
{ {
if (Ninja::isHosted()) { if (Ninja::isHosted()) {

View File

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

View File

@ -11,9 +11,9 @@ const RESOURCES = {
"favicon.png": "dca91c54388f52eded692718d5a98b8b", "favicon.png": "dca91c54388f52eded692718d5a98b8b",
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d", "flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
"/": "cb6f2c73612e3d57c56452ef7c10795a", "/": "b929c2274033670c857cf631017d7f84",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40", "manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"main.dart.js": "69afcf35930517b4949ed2f4d551307a", "main.dart.js": "b165f2dcfe57c6bae02dc84a3d527590",
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa", "assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1", "assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
"assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb", "assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb",

4978
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

4970
public/main.foss.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

View File

@ -41,6 +41,10 @@
color: #000; color: #000;
} }
#qr-bill td {
max-width: none;
}
.header-container { .header-container {
display: grid; display: grid;
grid-template-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;