From 17eaec31860861e1ddefb2e068c449832e4cfd8a Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 08:50:36 +1100 Subject: [PATCH 01/14] Grafting field stacks into templates --- app/Services/Pdf/PdfBuilder.php | 6 - app/Services/Pdf/PdfService.php | 4 +- app/Services/Template/TemplateService.php | 487 ++++++++++++++-------- 3 files changed, 319 insertions(+), 178 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index cae7ecaa94de..cc6631e5d542 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -1583,12 +1583,6 @@ class PdfBuilder // Dom Traversal /////////////////////////////////////// - - public function getSectionNode(string $selector) - { - return $this->document->getElementById($selector); - } - public function updateElementProperties() :self { foreach ($this->sections as $element) { diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index f680e389019e..1bf803edba87 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -62,11 +62,13 @@ class PdfService $this->document_type = $document_type; $this->options = $options; + + $this->start_time = microtime(true); + } public function boot(): self { - $this->start_time = microtime(true); $this->init(); diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 4efdc9d8f04b..461e6766bbb1 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -11,32 +11,31 @@ namespace App\Services\Template; -use App\Utils\Number; use App\Models\Client; +use App\Models\Company; use App\Models\Credit; use App\Models\Design; -use App\Models\Company; use App\Models\Invoice; use App\Models\Payment; use App\Models\Project; -use App\Models\Activity; -use App\Utils\HtmlEngine; -use League\Fractal\Manager; use App\Models\PurchaseOrder; -use App\Utils\VendorHtmlEngine; -use App\Utils\PaymentHtmlEngine; -use App\Utils\Traits\MakesDates; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\Traits\Pdf\PdfMaker; -use Twig\Extra\Intl\IntlExtension; -use App\Transformers\TaskTransformer; -use App\Transformers\QuoteTransformer; -use App\Services\Template\TemplateMock; -use App\Transformers\CreditTransformer; -use App\Transformers\InvoiceTransformer; +use App\Models\Quote; +use App\Models\RecurringInvoice; +use App\Models\Vendor; use App\Transformers\ProjectTransformer; use App\Transformers\PurchaseOrderTransformer; +use App\Transformers\QuoteTransformer; +use App\Transformers\TaskTransformer; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\HtmlEngine; +use App\Utils\Number; +use App\Utils\PaymentHtmlEngine; +use App\Utils\Traits\MakesDates; +use App\Utils\Traits\Pdf\PdfMaker; +use App\Utils\VendorHtmlEngine; +use League\Fractal\Manager; use League\Fractal\Serializer\ArraySerializer; +use Twig\Extra\Intl\IntlExtension; class TemplateService { @@ -54,6 +53,14 @@ class TemplateService public ?Company $company; + private ?Client $client; + + private ?Vendor $vendor; + + private Invoice | Quote | Credit | PurchaseOrder | RecurringInvoice $entity; + + private Payment $payment; + public function __construct(public ?Design $template = null) { $this->template = $template; @@ -71,7 +78,7 @@ class TemplateService $this->document->validateOnParse = true; $loader = new \Twig\Loader\FilesystemLoader(storage_path()); - $this->twig = new \Twig\Environment($loader,[ + $this->twig = new \Twig\Environment($loader, [ 'debug' => true, ]); $string_extension = new \Twig\Extension\StringLoaderExtension(); @@ -106,7 +113,7 @@ class TemplateService ->processData($data) ->parseNinjaBlocks() ->processVariables($data) - ->parseVariables(); + ->parseVariables(); return $this; } @@ -142,7 +149,12 @@ class TemplateService { return $this->compiled_html; } - + + /** + * Returns the PDF string + * + * @return mixed + */ public function getPdf(): mixed { @@ -155,12 +167,23 @@ class TemplateService return $pdf; } - + + /** + * Get the parsed data + * + * @return array + */ public function getData(): array { return $this->data; } - + + /** + * Process data variables + * + * @param mixed $data + * @return self + */ public function processData($data): self { @@ -171,7 +194,7 @@ class TemplateService /** * Parses all Ninja tags in the document - * + * * @return self */ private function parseNinjaBlocks(): self @@ -186,24 +209,19 @@ class TemplateService try { $template = $this->twig->createTemplate(html_entity_decode($template)); - } - catch(\Twig\Error\SyntaxError $e) { + } catch(\Twig\Error\SyntaxError $e) { nlog($e->getMessage()); throw ($e); - } - catch(\Twig\Error\Error $e) { + } catch(\Twig\Error\Error $e) { nlog("error = " .$e->getMessage()); throw ($e); - } - catch(\Twig\Error\RuntimeError $e) { + } catch(\Twig\Error\RuntimeError $e) { nlog("runtime = " .$e->getMessage()); throw ($e); - } - catch(\Twig\Error\LoaderError $e) { + } catch(\Twig\Error\LoaderError $e) { nlog("loader = " . $e->getMessage()); throw ($e); - } - catch(\Twig\Error\SecurityError $e) { + } catch(\Twig\Error\SecurityError $e) { nlog("security = " . $e->getMessage()); throw ($e); } @@ -229,7 +247,7 @@ class TemplateService /** * Parses all variables in the document - * + * * @return self */ private function parseVariables(): self @@ -239,8 +257,7 @@ class TemplateService foreach($this->variables as $key => $variable) { - if(isset($variable['labels']) && isset($variable['values'])) - { + if(isset($variable['labels']) && isset($variable['values'])) { $html = strtr($html, $variable['labels']); $html = strtr($html, $variable['values']); } @@ -271,8 +288,9 @@ class TemplateService */ private function compose(): self { - if(!$this->template) + if(!$this->template) { return $this; + } $html = ''; $html .= $this->template->design->includes; @@ -287,7 +305,7 @@ class TemplateService } /** - * Inject the template components + * Inject the template components * manually * * @return self @@ -319,11 +337,12 @@ class TemplateService $processed = []; - if(in_array($key, ['tasks','projects','aging']) || !$value->first() ) + if(in_array($key, ['tasks','projects','aging']) || !$value->first()) { return $processed; + } match ($key) { - 'variables' => $processed = $value->first() ?? [], + 'variables' => $processed = $value->first() ?? [], 'invoices' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], 'quotes' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], 'credits' => $processed = (new HtmlEngine($value->first()->invitations()->first()))->generateLabelsAndValues() ?? [], @@ -343,7 +362,7 @@ class TemplateService private function preProcessDataBlocks($data): array { - return collect($data)->map(function ($value, $key){ + return collect($data)->map(function ($value, $key) { $processed = []; @@ -367,75 +386,76 @@ class TemplateService public function processInvoices($invoices): array { $invoices = collect($invoices) - ->map(function ($invoice){ + ->map(function ($invoice) { - $payments = []; - - if($invoice->payments ?? false) { - $payments = $invoice->payments->map(function ($payment) { - return $this->transformPayment($payment); - })->toArray(); - } + $payments = []; + $this->entity = $invoice; - return [ - 'amount' => Number::formatMoney($invoice->amount, $invoice->client), - 'balance' => Number::formatMoney($invoice->balance, $invoice->client), - 'balance_raw' => $invoice->balance, - 'number' => $invoice->number ?: '', - 'discount' => $invoice->discount, - 'po_number' => $invoice->po_number ?: '', - 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()), - 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()), - 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()), - 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()), - 'terms' => $invoice->terms ?: '', - 'public_notes' => $invoice->public_notes ?: '', - 'private_notes' => $invoice->private_notes ?: '', - 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes, - 'tax_name1' => $invoice->tax_name1 ?? '', - 'tax_rate1' => (float) $invoice->tax_rate1, - 'tax_name2' => $invoice->tax_name2 ?? '', - 'tax_rate2' => (float) $invoice->tax_rate2, - 'tax_name3' => $invoice->tax_name3 ?? '', - 'tax_rate3' => (float) $invoice->tax_rate3, - 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client), - 'total_taxes_raw' => $invoice->total_taxes, - 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false, - 'footer' => $invoice->footer ?? '', - 'partial' => $invoice->partial ?? 0, - 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()), - 'custom_value1' => (string) $invoice->custom_value1 ?: '', - 'custom_value2' => (string) $invoice->custom_value2 ?: '', - 'custom_value3' => (string) $invoice->custom_value3 ?: '', - 'custom_value4' => (string) $invoice->custom_value4 ?: '', - 'custom_surcharge1' => (float) $invoice->custom_surcharge1, - 'custom_surcharge2' => (float) $invoice->custom_surcharge2, - 'custom_surcharge3' => (float) $invoice->custom_surcharge3, - 'custom_surcharge4' => (float) $invoice->custom_surcharge4, - 'exchange_rate' => (float) $invoice->exchange_rate, - 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1, - 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, - 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, - 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, - 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client): (array) [], - 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()), - 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client), - 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, - 'client' => [ - 'name' => $invoice->client->present()->name(), - 'balance' => $invoice->client->balance, - 'payment_balance' => $invoice->client->payment_balance, - 'credit_balance' => $invoice->client->credit_balance, - ], - 'payments' => $payments, - 'total_tax_map' => $invoice->calc()->getTotalTaxMap(), - 'line_tax_map' => $invoice->calc()->getTaxMap(), - ]; + if($invoice->payments ?? false) { + $payments = $invoice->payments->map(function ($payment) { + return $this->transformPayment($payment); + })->toArray(); + } - }); + return [ + 'amount' => Number::formatMoney($invoice->amount, $invoice->client), + 'balance' => Number::formatMoney($invoice->balance, $invoice->client), + 'balance_raw' => $invoice->balance, + 'number' => $invoice->number ?: '', + 'discount' => $invoice->discount, + 'po_number' => $invoice->po_number ?: '', + 'date' => $this->translateDate($invoice->date, $invoice->client->date_format(), $invoice->client->locale()), + 'last_sent_date' => $this->translateDate($invoice->last_sent_date, $invoice->client->date_format(), $invoice->client->locale()), + 'next_send_date' => $this->translateDate($invoice->next_send_date, $invoice->client->date_format(), $invoice->client->locale()), + 'due_date' => $this->translateDate($invoice->due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'terms' => $invoice->terms ?: '', + 'public_notes' => $invoice->public_notes ?: '', + 'private_notes' => $invoice->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $invoice->uses_inclusive_taxes, + 'tax_name1' => $invoice->tax_name1 ?? '', + 'tax_rate1' => (float) $invoice->tax_rate1, + 'tax_name2' => $invoice->tax_name2 ?? '', + 'tax_rate2' => (float) $invoice->tax_rate2, + 'tax_name3' => $invoice->tax_name3 ?? '', + 'tax_rate3' => (float) $invoice->tax_rate3, + 'total_taxes' => Number::formatMoney($invoice->total_taxes, $invoice->client), + 'total_taxes_raw' => $invoice->total_taxes, + 'is_amount_discount' => (bool) $invoice->is_amount_discount ?? false, + 'footer' => $invoice->footer ?? '', + 'partial' => $invoice->partial ?? 0, + 'partial_due_date' => $this->translateDate($invoice->partial_due_date, $invoice->client->date_format(), $invoice->client->locale()), + 'custom_value1' => (string) $invoice->custom_value1 ?: '', + 'custom_value2' => (string) $invoice->custom_value2 ?: '', + 'custom_value3' => (string) $invoice->custom_value3 ?: '', + 'custom_value4' => (string) $invoice->custom_value4 ?: '', + 'custom_surcharge1' => (float) $invoice->custom_surcharge1, + 'custom_surcharge2' => (float) $invoice->custom_surcharge2, + 'custom_surcharge3' => (float) $invoice->custom_surcharge3, + 'custom_surcharge4' => (float) $invoice->custom_surcharge4, + 'exchange_rate' => (float) $invoice->exchange_rate, + 'custom_surcharge_tax1' => (bool) $invoice->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, + 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client): (array) [], + 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()), + 'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client), + 'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled, + 'client' => [ + 'name' => $invoice->client->present()->name(), + 'balance' => $invoice->client->balance, + 'payment_balance' => $invoice->client->payment_balance, + 'credit_balance' => $invoice->client->credit_balance, + ], + 'payments' => $payments, + 'total_tax_map' => $invoice->calc()->getTotalTaxMap(), + 'line_tax_map' => $invoice->calc()->getTaxMap(), + ]; + + }); return $invoices->toArray(); @@ -443,7 +463,7 @@ class TemplateService public function padLineItems(array $items, Client $client): array { - return collect($items)->map(function ($item) use ($client){ + return collect($items)->map(function ($item) use ($client) { $item->cost_raw = $item->cost ?? 0; $item->discount_raw = $item->discount ?? 0; @@ -454,8 +474,9 @@ class TemplateService $item->cost = Number::formatMoney($item->cost_raw, $client); - if($item->is_amount_discount) + if($item->is_amount_discount) { $item->discount = Number::formatMoney($item->discount_raw, $client); + } $item->line_total = Number::formatMoney($item->line_total_raw, $client); $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client); @@ -471,7 +492,9 @@ class TemplateService { $data = []; - + + $this->payment = $payment; + $credits = $payment->credits->map(function ($credit) use ($payment) { return [ 'credit' => $credit->number, @@ -570,7 +593,7 @@ class TemplateService { return collect($payment->refund_meta ?? []) - ->map(function ($refund) use($payment){ + ->map(function ($refund) use ($payment) { $date = \Carbon\Carbon::parse($refund['date'])->addSeconds($payment->client->timezone_offset()); $date = $this->translateDate($date, $payment->client->date_format(), $payment->client->locale()); @@ -579,7 +602,7 @@ class TemplateService $map = []; foreach($refund['invoices'] as $refunded_invoice) { - $invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']); + $invoice = Invoice::withTrashed()->find($refunded_invoice['invoice_id']); $amount = Number::formatMoney($refunded_invoice['amount'], $payment->client); $notes = ctrans('texts.status_partially_refunded_amount', ['amount' => $amount]); @@ -623,67 +646,69 @@ class TemplateService public function processCredits($credits): array { $credits = collect($credits) - ->map(function ($credit){ + ->map(function ($credit) { - return [ - 'amount' => Number::formatMoney($credit->amount, $credit->client), - 'balance' => Number::formatMoney($credit->balance, $credit->client), - 'balance_raw' => $credit->balance, - 'number' => $credit->number ?: '', - 'discount' => $credit->discount, - 'po_number' => $credit->po_number ?: '', - 'date' => $this->translateDate($credit->date, $credit->client->date_format(), $credit->client->locale()), - 'last_sent_date' => $this->translateDate($credit->last_sent_date, $credit->client->date_format(), $credit->client->locale()), - 'next_send_date' => $this->translateDate($credit->next_send_date, $credit->client->date_format(), $credit->client->locale()), - 'due_date' => $this->translateDate($credit->due_date, $credit->client->date_format(), $credit->client->locale()), - 'terms' => $credit->terms ?: '', - 'public_notes' => $credit->public_notes ?: '', - 'private_notes' => $credit->private_notes ?: '', - 'uses_inclusive_taxes' => (bool) $credit->uses_inclusive_taxes, - 'tax_name1' => $credit->tax_name1 ?? '', - 'tax_rate1' => (float) $credit->tax_rate1, - 'tax_name2' => $credit->tax_name2 ?? '', - 'tax_rate2' => (float) $credit->tax_rate2, - 'tax_name3' => $credit->tax_name3 ?? '', - 'tax_rate3' => (float) $credit->tax_rate3, - 'total_taxes' => Number::formatMoney($credit->total_taxes, $credit->client), - 'total_taxes_raw' => $credit->total_taxes, - 'is_amount_discount' => (bool) $credit->is_amount_discount ?? false, - 'footer' => $credit->footer ?? '', - 'partial' => $credit->partial ?? 0, - 'partial_due_date' => $this->translateDate($credit->partial_due_date, $credit->client->date_format(), $credit->client->locale()), - 'custom_value1' => (string) $credit->custom_value1 ?: '', - 'custom_value2' => (string) $credit->custom_value2 ?: '', - 'custom_value3' => (string) $credit->custom_value3 ?: '', - 'custom_value4' => (string) $credit->custom_value4 ?: '', - 'custom_surcharge1' => (float) $credit->custom_surcharge1, - 'custom_surcharge2' => (float) $credit->custom_surcharge2, - 'custom_surcharge3' => (float) $credit->custom_surcharge3, - 'custom_surcharge4' => (float) $credit->custom_surcharge4, - 'exchange_rate' => (float) $credit->exchange_rate, - 'custom_surcharge_tax1' => (bool) $credit->custom_surcharge_tax1, - 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2, - 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3, - 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4, - 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client): (array) [], - 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()), - 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()), - 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()), - 'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()), - 'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client), - 'auto_bill_enabled' => (bool) $credit->auto_bill_enabled, - 'client' => [ - 'name' => $credit->client->present()->name(), - 'balance' => $credit->client->balance, - 'payment_balance' => $credit->client->payment_balance, - 'credit_balance' => $credit->client->credit_balance, - ], - 'payments' => [], - 'total_tax_map' => $credit->calc()->getTotalTaxMap(), - 'line_tax_map' => $credit->calc()->getTaxMap(), - ]; + $this->entity = $credit; + + return [ + 'amount' => Number::formatMoney($credit->amount, $credit->client), + 'balance' => Number::formatMoney($credit->balance, $credit->client), + 'balance_raw' => $credit->balance, + 'number' => $credit->number ?: '', + 'discount' => $credit->discount, + 'po_number' => $credit->po_number ?: '', + 'date' => $this->translateDate($credit->date, $credit->client->date_format(), $credit->client->locale()), + 'last_sent_date' => $this->translateDate($credit->last_sent_date, $credit->client->date_format(), $credit->client->locale()), + 'next_send_date' => $this->translateDate($credit->next_send_date, $credit->client->date_format(), $credit->client->locale()), + 'due_date' => $this->translateDate($credit->due_date, $credit->client->date_format(), $credit->client->locale()), + 'terms' => $credit->terms ?: '', + 'public_notes' => $credit->public_notes ?: '', + 'private_notes' => $credit->private_notes ?: '', + 'uses_inclusive_taxes' => (bool) $credit->uses_inclusive_taxes, + 'tax_name1' => $credit->tax_name1 ?? '', + 'tax_rate1' => (float) $credit->tax_rate1, + 'tax_name2' => $credit->tax_name2 ?? '', + 'tax_rate2' => (float) $credit->tax_rate2, + 'tax_name3' => $credit->tax_name3 ?? '', + 'tax_rate3' => (float) $credit->tax_rate3, + 'total_taxes' => Number::formatMoney($credit->total_taxes, $credit->client), + 'total_taxes_raw' => $credit->total_taxes, + 'is_amount_discount' => (bool) $credit->is_amount_discount ?? false, + 'footer' => $credit->footer ?? '', + 'partial' => $credit->partial ?? 0, + 'partial_due_date' => $this->translateDate($credit->partial_due_date, $credit->client->date_format(), $credit->client->locale()), + 'custom_value1' => (string) $credit->custom_value1 ?: '', + 'custom_value2' => (string) $credit->custom_value2 ?: '', + 'custom_value3' => (string) $credit->custom_value3 ?: '', + 'custom_value4' => (string) $credit->custom_value4 ?: '', + 'custom_surcharge1' => (float) $credit->custom_surcharge1, + 'custom_surcharge2' => (float) $credit->custom_surcharge2, + 'custom_surcharge3' => (float) $credit->custom_surcharge3, + 'custom_surcharge4' => (float) $credit->custom_surcharge4, + 'exchange_rate' => (float) $credit->exchange_rate, + 'custom_surcharge_tax1' => (bool) $credit->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4, + 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client): (array) [], + 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()), + 'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()), + 'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client), + 'auto_bill_enabled' => (bool) $credit->auto_bill_enabled, + 'client' => [ + 'name' => $credit->client->present()->name(), + 'balance' => $credit->client->balance, + 'payment_balance' => $credit->client->payment_balance, + 'credit_balance' => $credit->client->credit_balance, + ], + 'payments' => [], + 'total_tax_map' => $credit->calc()->getTotalTaxMap(), + 'line_tax_map' => $credit->calc()->getTaxMap(), + ]; - }); + }); return $credits->toArray(); @@ -775,5 +800,125 @@ class TemplateService return $this; } + + /** + * Parses and finds any stacks to replace + * + * @return self + */ + private function parseGlobalStacks(): self + { + $stacks = [ + 'entity-details', + 'client-details', + 'vendor-details', + 'company-details', + 'company-address', + 'shipping-details', + ]; -} \ No newline at end of file + collect($stacks)->filter(function ($stack) { + $this->document->getElementById($stack); + + })->each(function ($stack){ + $this->parseStack($stack); + }); + + return $this; + + } + + /** + * Injects field stacks into Template + * + * @param string $stack + * @return self + */ + private function parseStack(string $stack): self + { + match($stack){ + 'entity-details' => $this->entityDetails(), + 'client-details' => $this->clientDetails(), + 'vendor-details' => $this->vendorDetails(), + 'company-details' => $this->companyDetails(), + 'company-address' => $this->companyAddress(), + 'shipping-details' => $this->shippingDetails(), + }; + + return $this; + } + + private function companyDetails(): self + { + + collect($this->company->settings->pdf_variables['company_details']) + ->filter(function ($variable) { + return isset($this->variables['values'][$variable]) && !empty($this->variables['values'][$variable]); + }) + ->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + }); + + + return $this; + } + + private function companyAddress(): self + { + + $variables = $this->company->settings->pdf_variables['company_address']; + + $elements = []; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; + } + + return $elements; + + return $this; + } + + private function shippingDetails(): self + { + + return $this; + } + + private function clientDetails(): self + { + + return $this; + } + + private function entityDetails(): self + { + + return $this; + } + + private function vendorDetails(): self + { + + + $elements = []; + + if (!$this->vendor) { + return $elements; + } + + $variables = $this->context['pdf_variables']['vendor_details']; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + } + + return $elements; + + + return $this; + } + + + +} From d5ba3bcf46fa574dd27f80ea9b3598b158a80402 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 12:54:29 +1100 Subject: [PATCH 02/14] Fixes for stacked fields --- app/Services/Template/TemplateService.php | 165 ++++++++++++++++++---- tests/Feature/Template/TemplateTest.php | 30 ++-- 2 files changed, 159 insertions(+), 36 deletions(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 461e6766bbb1..d17287d7e93a 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -11,31 +11,32 @@ namespace App\Services\Template; +use App\Models\Quote; +use App\Utils\Number; use App\Models\Client; -use App\Models\Company; use App\Models\Credit; use App\Models\Design; +use App\Models\Vendor; +use App\Models\Company; use App\Models\Invoice; use App\Models\Payment; use App\Models\Project; -use App\Models\PurchaseOrder; -use App\Models\Quote; -use App\Models\RecurringInvoice; -use App\Models\Vendor; -use App\Transformers\ProjectTransformer; -use App\Transformers\PurchaseOrderTransformer; -use App\Transformers\QuoteTransformer; -use App\Transformers\TaskTransformer; -use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; -use App\Utils\Number; +use League\Fractal\Manager; +use App\Models\PurchaseOrder; +use App\Utils\VendorHtmlEngine; +use App\Models\RecurringInvoice; use App\Utils\PaymentHtmlEngine; use App\Utils\Traits\MakesDates; +use App\Utils\HostedPDF\NinjaPdf; use App\Utils\Traits\Pdf\PdfMaker; -use App\Utils\VendorHtmlEngine; -use League\Fractal\Manager; -use League\Fractal\Serializer\ArraySerializer; use Twig\Extra\Intl\IntlExtension; +use App\Transformers\TaskTransformer; +use App\Transformers\QuoteTransformer; +use App\Transformers\ProjectTransformer; +use League\CommonMark\CommonMarkConverter; +use App\Transformers\PurchaseOrderTransformer; +use League\Fractal\Serializer\ArraySerializer; class TemplateService { @@ -61,6 +62,8 @@ class TemplateService private Payment $payment; + private CommonMarkConverter $commonmark; + public function __construct(public ?Design $template = null) { $this->template = $template; @@ -74,6 +77,12 @@ class TemplateService */ private function init(): self { + + + $this->commonmark = new CommonMarkConverter([ + 'allow_unsafe_links' => false, + ]); + $this->document = new \DOMDocument(); $this->document->validateOnParse = true; @@ -111,6 +120,7 @@ class TemplateService { $this->compose() ->processData($data) + ->parseGlobalStacks() ->parseNinjaBlocks() ->processVariables($data) ->parseVariables(); @@ -135,6 +145,7 @@ class TemplateService $this->parseNinjaBlocks() + ->parseGlobalStacks() ->parseVariables(); return $this; @@ -563,8 +574,6 @@ class TemplateService 'refund_activity' => $this->getPaymentRefundActivity($payment), ]; - nlog($data); - return $data; } @@ -806,7 +815,7 @@ class TemplateService * * @return self */ - private function parseGlobalStacks(): self + public function parseGlobalStacks(): self { $stacks = [ 'entity-details', @@ -857,7 +866,7 @@ class TemplateService }) ->map(function ($variable) { return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; - }); + })->toArray(); return $this; @@ -901,24 +910,130 @@ class TemplateService { - $elements = []; + // $elements = []; - if (!$this->vendor) { - return $elements; + // if (!$this->vendor) { + // return $elements; + // } + + // $variables = $this->context['pdf_variables']['vendor_details']; + + // foreach ($variables as $variable) { + // $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + // } + + // return $elements; + + + return $this; + } + + //////////////////////////////////////// + // Dom Traversal + /////////////////////////////////////// + + public function updateElementProperties(string $element_id, array $elements): self + { + $node = $this->document->getElementById($element_id); + + $this->createElementContent($node, $elements); + + return $this; + } + + public function updateElementProperty($element, string $attribute, ?string $value) + { + + if ($attribute == 'hidden' && ($value == false || $value == 'false')) { + return $element; } - $variables = $this->context['pdf_variables']['vendor_details']; + $element->setAttribute($attribute, $value); - foreach ($variables as $variable) { - $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + if ($element->getAttribute($attribute) === $value) { + return $element; } - return $elements; + return $element; + } + + public function createElementContent($element, $children) :self + { + foreach ($children as $child) { + $contains_html = false; + + if ($child['element'] !== 'script') { + if ($this->company->markdown_enabled && array_key_exists('content', $child)) { + $child['content'] = str_replace('
', "\r", $child['content']); + $child['content'] = $this->commonmark->convert($child['content'] ?? ''); + } + } + + if (isset($child['content'])) { + if (isset($child['is_empty']) && $child['is_empty'] === true) { + continue; + } + + $contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0; + } + + if ($contains_html) { + // If the element contains the HTML, we gonna display it as is. Backend is going to + // encode it for us, preventing any errors on the processing stage. + // Later, we decode this using Javascript so it looks like it's normal HTML being injected. + // To get all elements that need frontend decoding, we use 'data-state' property. + + $_child = $this->document->createElement($child['element'], ''); + $_child->setAttribute('data-state', 'encoded-html'); + $_child->nodeValue = htmlspecialchars($child['content']); + } else { + // .. in case string doesn't contain any HTML, we'll just return + // raw $content. + + $_child = $this->document->createElement($child['element'], isset($child['content']) ? htmlspecialchars($child['content']) : ''); + } + + $element->appendChild($_child); + + if (isset($child['properties'])) { + foreach ($child['properties'] as $property => $value) { + $this->updateElementProperty($_child, $property, $value); + } + } + + if (isset($child['elements'])) { + $this->createElementContent($_child, $child['elements']); + } + } return $this; } + + + + + + + + + + + + + + + + + + + + + + + + } diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php index 39109ce8855b..2268891fadb9 100644 --- a/tests/Feature/Template/TemplateTest.php +++ b/tests/Feature/Template/TemplateTest.php @@ -165,6 +165,8 @@ class TemplateTest extends TestCase '; + private string $stack = '
'; + protected function setUp() :void { parent::setUp(); @@ -177,6 +179,23 @@ class TemplateTest extends TestCase } + public function testStackResolution() + { + + $partials['design']['includes'] = ''; + $partials['design']['header'] = ''; + $partials['design']['body'] = $this->stack; + $partials['design']['footer'] = ''; + + $ts = new TemplateService(); + $x = $ts->setTemplate($partials) + ->setCompany($this->company) + ->parseGlobalStacks() + ->getHtml(); + + nlog($x); + } + public function testDataMaps() { $start = microtime(true); @@ -319,19 +338,8 @@ class TemplateTest extends TestCase ]; }); - $queries = \DB::getQueryLog(); - $count = count($queries); - - nlog("query count = {$count}"); - $x = $invoices->toArray(); - // nlog(json_encode($x)); - // nlog(json_encode(htmlspecialchars(json_encode($x), ENT_QUOTES, 'UTF-8'))); - // nlog($invoices->toJson()); - $this->assertIsArray($invoices->toArray()); - nlog("end invoices = " . microtime(true) - $start); - } private function transformPayment(Payment $payment): array From 5955544464b12ebb9cf66ba1620c5fa7c1b9e05d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 12:56:43 +1100 Subject: [PATCH 03/14] Fixes for stacked fields --- app/Services/Template/TemplateService.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index d17287d7e93a..bdf1e9ed15c5 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -860,6 +860,7 @@ class TemplateService private function companyDetails(): self { + $company_details = collect($this->company->settings->pdf_variables['company_details']) ->filter(function ($variable) { return isset($this->variables['values'][$variable]) && !empty($this->variables['values'][$variable]); @@ -869,6 +870,7 @@ class TemplateService })->toArray(); + $this->parseStack() return $this; } From d4bd6fc7d6ee9f0ad03363d9894d2c48eecf0a7f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 15:47:16 +1100 Subject: [PATCH 04/14] Additions for Template Field Stacks --- app/Http/Controllers/PreviewController.php | 17 +- app/Services/Template/TemplateService.php | 232 +++++- composer.lock | 785 ++++++++++----------- tests/Feature/Template/TemplateTest.php | 43 +- 4 files changed, 625 insertions(+), 452 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index d5cf2651f002..fae8aefea366 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -361,21 +361,8 @@ class PreviewController extends BaseController $design_object = json_decode(json_encode(request()->input('design')),1); - // $client_id = Invoice::whereHas('payments')->company()->where('is_deleted', 0)->orderBy('id','desc')->first()->client_id; - // $vendor_id = PurchaseOrder::query()->company()->where('is_deleted', 0)->orderBy('id', 'desc')->first()->vendor_id; - - // $data = [ - // 'invoices' => Invoice::whereHas('payments')->company()->with('client','payments')->where('client_id', $client_id)->orderBy('id','desc')->take(4)->get(), - // 'quotes' => Quote::query()->company()->with('client')->where('client_id', $client_id)->orderBy('id','desc')->take(4)->get(), - // 'credits' => Credit::query()->company()->with('client')->where('client_id', $client_id)->orderBy('id','desc')->take(4)->get(), - // 'payments' => Payment::query()->company()->with('client')->where('client_id', $client_id)->orderBy('id','desc')->take(4)->get(), - // 'purchase_orders' => PurchaseOrder::query()->company()->with('vendor')->where('vendor_id', $vendor_id)->orderBy('id','desc')->take(5)->get(), - // 'tasks' => Task::query()->company()->with('client','invoice')->where('client_id', $client_id)->orderBy('id','desc')->take(2)->get(), - // 'projects' => Project::query()->company()->with('tasks','client')->where('client_id', $client_id)->orderBy('id','desc')->take(2)->get(), - // ]; - - $ts = (new TemplateService()); + try { $ts->setCompany($company) ->setTemplate($design_object) @@ -383,9 +370,7 @@ class PreviewController extends BaseController } catch(\Twig\Error\SyntaxError $e) { - // return response()->json(['message' => 'Twig syntax is invalid.', 'errors' => new \stdClass], 422); - } $html = $ts->getHtml(); diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index bdf1e9ed15c5..bf1b62507d37 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -140,10 +140,11 @@ class TemplateService $tm = new TemplateMock($this->company); $tm->init(); + $this->entity = $this->company->invoices()->first(); + $this->data = $tm->engines; $this->variables = $tm->variables[0]; - $this->parseNinjaBlocks() ->parseGlobalStacks() ->parseVariables(); @@ -261,13 +262,12 @@ class TemplateService * * @return self */ - private function parseVariables(): self + public function parseVariables(): self { $html = $this->getHtml(); foreach($this->variables as $key => $variable) { - if(isset($variable['labels']) && isset($variable['values'])) { $html = strtr($html, $variable['labels']); $html = strtr($html, $variable['values']); @@ -827,12 +827,11 @@ class TemplateService ]; collect($stacks)->filter(function ($stack) { - $this->document->getElementById($stack); - + return $this->document->getElementById($stack) ?? false; })->each(function ($stack){ $this->parseStack($stack); }); - + return $this; } @@ -845,6 +844,7 @@ class TemplateService */ private function parseStack(string $stack): self { + match($stack){ 'entity-details' => $this->entityDetails(), 'client-details' => $this->clientDetails(), @@ -854,82 +854,233 @@ class TemplateService 'shipping-details' => $this->shippingDetails(), }; + $this->save(); + return $this; } private function companyDetails(): self { + $var_set = $this->getVarSet(); $company_details = - collect($this->company->settings->pdf_variables['company_details']) - ->filter(function ($variable) { - return isset($this->variables['values'][$variable]) && !empty($this->variables['values'][$variable]); + collect($this->company->settings->pdf_variables->company_details) + ->filter(function ($variable) use($var_set) { + return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); }) ->map(function ($variable) { return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; })->toArray(); - $this->parseStack() + $this->updateElementProperties('company-details', $company_details); + return $this; } private function companyAddress(): self { - $variables = $this->company->settings->pdf_variables['company_address']; + $var_set = $this->getVarSet(); - $elements = []; + $company_address = + collect($this->company->settings->pdf_variables->company_address) + ->filter(function ($variable) use ($var_set) { + return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); + }) + ->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; + })->toArray(); - foreach ($variables as $variable) { - $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; - } - - return $elements; + $this->updateElementProperties('company-address', $company_address); return $this; } private function shippingDetails(): self { + if(!$this->entity->client) + return $this; + + $this->client = $this->entity->client; + + $shipping_address = [ + ['element' => 'p', 'content' => ctrans('texts.shipping_address'), 'properties' => ['data-ref' => 'shipping_address-label', 'style' => 'font-weight: bold; text-transform: uppercase']], + ['element' => 'p', 'content' => $this->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.name']], + ['element' => 'p', 'content' => $this->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address1']], + ['element' => 'p', 'content' => $this->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'shipping_address-client.shipping_address2']], + ['element' => 'p', 'show_empty' => false, 'elements' => [ + ['element' => 'span', 'content' => "{$this->client->shipping_city} ", 'properties' => ['ref' => 'shipping_address-client.shipping_city']], + ['element' => 'span', 'content' => "{$this->client->shipping_state} ", 'properties' => ['ref' => 'shipping_address-client.shipping_state']], + ['element' => 'span', 'content' => "{$this->client->shipping_postal_code} ", 'properties' => ['ref' => 'shipping_address-client.shipping_postal_code']], + ]], + ['element' => 'p', 'content' => optional($this->client->shipping_country)->name, 'show_empty' => false], + ]; + + $shipping_address = + collect($shipping_address)->filter(function ($address){ + return isset($address['content']) && !empty($address['content']); + })->toArray(); + + $this->updateElementProperties('shipping-details', $shipping_address); return $this; } private function clientDetails(): self { + $var_set = $this->getVarSet(); + + $client_details = + collect($this->company->settings->pdf_variables->client_details) + ->filter(function ($variable) use ($var_set) { + return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); + }) + ->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; + })->toArray(); + + + $this->updateElementProperties('client-details', $client_details); return $this; } + private function resolveEntity(): string + { + $entity_string = ''; + + match($this->entity){ + ($this->entity instanceof Invoice) => $entity_string = 'invoice', + ($this->entity instanceof Quote) => $entity_string = 'quote', + ($this->entity instanceof Credit) => $entity_string = 'credit', + ($this->entity instanceof RecurringInvoice) => $entity_string = 'invoice', + ($this->entity instanceof PurchaseOrder) => $entity_string = 'task', + default => $entity_string = 'invoice', + }; + + return $entity_string; + + } + + /** + * Returns the variable array by first key, if it exists + * + * @return array + */ + private function getVarSet(): array + { + return array_key_exists(array_key_first($this->variables), $this->variables) ? $this->variables[array_key_first($this->variables)] : $this->variables; + } + + /** + * Injects the entity details to the DOM document + * + * @return self + */ private function entityDetails(): self { + $entity_string = $this->resolveEntity(); + $entity_string_prop = "{$entity_string}_details"; + $var_set = $this->getVarSet(); + + $entity_details = + collect($this->company->settings->pdf_variables->{$entity_string_prop}) + ->filter(function ($variable) use ($var_set) { + return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); + })->toArray(); + + $this->updateElementProperties("entity-details", $this->labelledFieldStack($entity_details)); return $this; } + private function labelledFieldStack(array $variables): array + { + + $elements = []; + + foreach ($variables as $variable) { + $_variable = explode('.', $variable)[1]; + $_customs = ['custom1', 'custom2', 'custom3', 'custom4']; + + $var = str_replace("custom", "custom_value", $_variable); + + if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { + $elements[] = ['element' => 'tr', 'elements' => [ + ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]], + ]]; + } else { + $elements[] = ['element' => 'tr', 'properties' => ['hidden' => $this->entityVariableCheck($variable)], 'elements' => [ + ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], + ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]], + ]]; + } + } + + return $elements; + + } + private function vendorDetails(): self { + $var_set = $this->getVarSet(); - // $elements = []; + $vendor_details = + collect($this->company->settings->pdf_variables->vendor_details) + ->filter(function ($variable) use ($var_set) { + return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); + }) + ->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + })->toArray(); - // if (!$this->vendor) { - // return $elements; - // } - - // $variables = $this->context['pdf_variables']['vendor_details']; - - // foreach ($variables as $variable) { - // $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; - // } - - // return $elements; + $this->updateElementProperties('vendor-details', $vendor_details); return $this; } + + /** + * Performs a variable check to ensure + * the variable exists + * + * @param string $variable + * @return bool + * + */ + public function entityVariableCheck(string $variable): bool + { + // When it comes to invoice balance, we'll always show it. + if ($variable == '$invoice.total') { + return false; + } + + // Some variables don't map 1:1 to table columns. This gives us support for such cases. + $aliases = [ + '$quote.balance_due' => 'partial', + ]; + + try { + $_variable = explode('.', $variable)[1]; + } catch (\Exception $e) { + throw new \Exception('Company settings seems to be broken. Missing $this->service->config->entity.variable type.'); + } + + if (\in_array($variable, \array_keys($aliases))) { + $_variable = $aliases[$variable]; + } + + if (is_null($this->entity->{$_variable}) || empty($this->entity->{$_variable})) { + return true; + } + + return false; + } + //////////////////////////////////////// // Dom Traversal /////////////////////////////////////// @@ -937,9 +1088,9 @@ class TemplateService public function updateElementProperties(string $element_id, array $elements): self { $node = $this->document->getElementById($element_id); - - $this->createElementContent($node, $elements); - + + $this->createElementContent($node, $elements); + return $this; } @@ -962,15 +1113,17 @@ class TemplateService public function createElementContent($element, $children) :self { + foreach ($children as $child) { $contains_html = false; - if ($child['element'] !== 'script') { - if ($this->company->markdown_enabled && array_key_exists('content', $child)) { - $child['content'] = str_replace('
', "\r", $child['content']); - $child['content'] = $this->commonmark->convert($child['content'] ?? ''); - } - } + //06-11-2023 for some reason this parses content as HTML + // if ($child['element'] !== 'script') { + // if ($this->company->markdown_enabled && array_key_exists('content', $child)) { + // $child['content'] = str_replace('
', "\r", $child['content']); + // $child['content'] = $this->commonmark->convert($child['content'] ?? ''); + // } + // } if (isset($child['content'])) { if (isset($child['is_empty']) && $child['is_empty'] === true) { @@ -979,7 +1132,7 @@ class TemplateService $contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0; } - + if ($contains_html) { // If the element contains the HTML, we gonna display it as is. Backend is going to // encode it for us, preventing any errors on the processing stage. @@ -1007,6 +1160,7 @@ class TemplateService if (isset($child['elements'])) { $this->createElementContent($_child, $child['elements']); } + } return $this; diff --git a/composer.lock b/composer.lock index 2c2cacba4624..20bef4724e05 100644 --- a/composer.lock +++ b/composer.lock @@ -60,16 +60,16 @@ }, { "name": "apimatic/core", - "version": "0.3.2", + "version": "0.3.3", "source": { "type": "git", "url": "https://github.com/apimatic/core-lib-php.git", - "reference": "32238fb83ce9a3ebef38c726b497c0f218d6e6c9" + "reference": "984123c831598fc31749d194aa044cd46f227d29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/apimatic/core-lib-php/zipball/32238fb83ce9a3ebef38c726b497c0f218d6e6c9", - "reference": "32238fb83ce9a3ebef38c726b497c0f218d6e6c9", + "url": "https://api.github.com/repos/apimatic/core-lib-php/zipball/984123c831598fc31749d194aa044cd46f227d29", + "reference": "984123c831598fc31749d194aa044cd46f227d29", "shasum": "" }, "require": { @@ -107,9 +107,9 @@ ], "support": { "issues": "https://github.com/apimatic/core-lib-php/issues", - "source": "https://github.com/apimatic/core-lib-php/tree/0.3.2" + "source": "https://github.com/apimatic/core-lib-php/tree/0.3.3" }, - "time": "2023-07-11T09:30:32+00:00" + "time": "2023-10-26T06:52:40+00:00" }, { "name": "apimatic/core-interfaces", @@ -431,16 +431,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.2", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9" + "reference": "5545a4fa310aec39f54279fdacebcce33b3ff382" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9", - "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/5545a4fa310aec39f54279fdacebcce33b3ff382", + "reference": "5545a4fa310aec39f54279fdacebcce33b3ff382", "shasum": "" }, "require": { @@ -479,26 +479,26 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.3" }, - "time": "2023-07-20T16:49:55+00:00" + "time": "2023-10-16T20:10:06+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.282.0", + "version": "3.284.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "79a3ed5bb573f592823f8b1cffe0dbac3132e6b4" + "reference": "1b7e926acc990509e3d13fa708cdede686b40d90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/79a3ed5bb573f592823f8b1cffe0dbac3132e6b4", - "reference": "79a3ed5bb573f592823f8b1cffe0dbac3132e6b4", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1b7e926acc990509e3d13fa708cdede686b40d90", + "reference": "1b7e926acc990509e3d13fa708cdede686b40d90", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.4", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", @@ -574,9 +574,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.282.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.284.0" }, - "time": "2023-09-28T18:09:20+00:00" + "time": "2023-11-03T18:13:48+00:00" }, { "name": "bacon/bacon-qr-code", @@ -686,16 +686,16 @@ }, { "name": "braintree/braintree_php", - "version": "6.13.0", + "version": "6.14.0", "source": { "type": "git", "url": "https://github.com/braintree/braintree_php.git", - "reference": "c7bdef50c9692f1f9f9bf982e452aec0d137f5ec" + "reference": "084ed5bb728bc32ad444c6d043c87b409cca72d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/braintree/braintree_php/zipball/c7bdef50c9692f1f9f9bf982e452aec0d137f5ec", - "reference": "c7bdef50c9692f1f9f9bf982e452aec0d137f5ec", + "url": "https://api.github.com/repos/braintree/braintree_php/zipball/084ed5bb728bc32ad444c6d043c87b409cca72d7", + "reference": "084ed5bb728bc32ad444c6d043c87b409cca72d7", "shasum": "" }, "require": { @@ -729,9 +729,9 @@ "description": "Braintree PHP Client Library", "support": { "issues": "https://github.com/braintree/braintree_php/issues", - "source": "https://github.com/braintree/braintree_php/tree/6.13.0" + "source": "https://github.com/braintree/braintree_php/tree/6.14.0" }, - "time": "2023-08-30T21:46:13+00:00" + "time": "2023-10-18T22:00:15+00:00" }, { "name": "brick/math", @@ -790,16 +790,16 @@ }, { "name": "checkout/checkout-sdk-php", - "version": "3.0.15", + "version": "3.0.17", "source": { "type": "git", "url": "https://github.com/checkout/checkout-sdk-php.git", - "reference": "18a2278eb28cb1141b1cb189d4a2ee86b1837350" + "reference": "dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/18a2278eb28cb1141b1cb189d4a2ee86b1837350", - "reference": "18a2278eb28cb1141b1cb189d4a2ee86b1837350", + "url": "https://api.github.com/repos/checkout/checkout-sdk-php/zipball/dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27", + "reference": "dabb6dd37ad80aaa9c34e60f48f9bf8b651bdc27", "shasum": "" }, "require": { @@ -852,9 +852,9 @@ ], "support": { "issues": "https://github.com/checkout/checkout-sdk-php/issues", - "source": "https://github.com/checkout/checkout-sdk-php/tree/3.0.15" + "source": "https://github.com/checkout/checkout-sdk-php/tree/3.0.17" }, - "time": "2023-09-19T14:42:51+00:00" + "time": "2023-10-20T22:35:30+00:00" }, { "name": "cleverit/ubl_invoice", @@ -1353,16 +1353,16 @@ }, { "name": "doctrine/dbal", - "version": "3.7.0", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", - "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5b7bd66c9ff58c04c5474ab85edce442f8081cb2", + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2", "shasum": "" }, "require": { @@ -1446,7 +1446,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.7.0" + "source": "https://github.com/doctrine/dbal/tree/3.7.1" }, "funding": [ { @@ -1462,7 +1462,7 @@ "type": "tidelift" } ], - "time": "2023-09-26T20:56:55+00:00" + "time": "2023-10-06T05:06:20+00:00" }, { "name": "doctrine/deprecations", @@ -1903,16 +1903,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", "shasum": "" }, "require": { @@ -1921,8 +1921,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1958,7 +1958,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" }, "funding": [ { @@ -1966,7 +1966,7 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2023-10-06T06:47:41+00:00" }, { "name": "endroid/qr-code", @@ -2169,16 +2169,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.8.1", + "version": "v6.9.0", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26" + "reference": "f03270e63eaccf3019ef0f32849c497385774e11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5dbc8959427416b8ee09a100d7a8588c00fb2e26", - "reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/f03270e63eaccf3019ef0f32849c497385774e11", + "reference": "f03270e63eaccf3019ef0f32849c497385774e11", "shasum": "" }, "require": { @@ -2226,27 +2226,27 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.8.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.9.0" }, - "time": "2023-07-14T18:33:00+00:00" + "time": "2023-10-05T00:24:42+00:00" }, { "name": "fruitcake/php-cors", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6" + "symfony/http-foundation": "^4.4|^5.4|^6|^7" }, "require-dev": { "phpstan/phpstan": "^1.4", @@ -2256,7 +2256,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -2287,7 +2287,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" }, "funding": [ { @@ -2299,7 +2299,7 @@ "type": "github" } ], - "time": "2022-02-20T15:07:15+00:00" + "time": "2023-10-12T05:21:21+00:00" }, { "name": "gocardless/gocardless-pro", @@ -2487,16 +2487,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.318.0", + "version": "v0.323.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "908a866797b9731352e650997112c8c3a0347ac5" + "reference": "d5497d30ddfafe7592102ca48bedaf222a4ca7a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/908a866797b9731352e650997112c8c3a0347ac5", - "reference": "908a866797b9731352e650997112c8c3a0347ac5", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/d5497d30ddfafe7592102ca48bedaf222a4ca7a6", + "reference": "d5497d30ddfafe7592102ca48bedaf222a4ca7a6", "shasum": "" }, "require": { @@ -2525,22 +2525,22 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.318.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.323.0" }, - "time": "2023-10-02T01:10:14+00:00" + "time": "2023-11-06T01:08:38+00:00" }, { "name": "google/auth", - "version": "v1.30.0", + "version": "v1.32.1", "source": { "type": "git", "url": "https://github.com/googleapis/google-auth-library-php.git", - "reference": "6028b072aa444d7edecbed603431322026704627" + "reference": "999e9ce8b9d17914f04e1718271a0a46da4de2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/6028b072aa444d7edecbed603431322026704627", - "reference": "6028b072aa444d7edecbed603431322026704627", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/999e9ce8b9d17914f04e1718271a0a46da4de2f3", + "reference": "999e9ce8b9d17914f04e1718271a0a46da4de2f3", "shasum": "" }, "require": { @@ -2583,9 +2583,9 @@ "support": { "docs": "https://googleapis.github.io/google-auth-library-php/main/", "issues": "https://github.com/googleapis/google-auth-library-php/issues", - "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.30.0" + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.32.1" }, - "time": "2023-09-07T19:13:44+00:00" + "time": "2023-10-17T21:13:22+00:00" }, { "name": "graham-campbell/result-type", @@ -3397,16 +3397,16 @@ }, { "name": "horstoeko/zugferd", - "version": "v1.0.30", + "version": "v1.0.31", "source": { "type": "git", "url": "https://github.com/horstoeko/zugferd.git", - "reference": "b5e85651fe2e53eef82aa086c9245b7f5229433f" + "reference": "d1cdb2100aed1cec67b25a660da5e4cc31cf7e09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/b5e85651fe2e53eef82aa086c9245b7f5229433f", - "reference": "b5e85651fe2e53eef82aa086c9245b7f5229433f", + "url": "https://api.github.com/repos/horstoeko/zugferd/zipball/d1cdb2100aed1cec67b25a660da5e4cc31cf7e09", + "reference": "d1cdb2100aed1cec67b25a660da5e4cc31cf7e09", "shasum": "" }, "require": { @@ -3464,9 +3464,9 @@ ], "support": { "issues": "https://github.com/horstoeko/zugferd/issues", - "source": "https://github.com/horstoeko/zugferd/tree/v1.0.30" + "source": "https://github.com/horstoeko/zugferd/tree/v1.0.31" }, - "time": "2023-09-30T13:42:02+00:00" + "time": "2023-10-12T16:05:51+00:00" }, { "name": "http-interop/http-factory-guzzle", @@ -4287,16 +4287,16 @@ }, { "name": "laravel/framework", - "version": "v10.25.2", + "version": "v10.30.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6014dd456b414b305fb0b408404efdcec18e64bc" + "reference": "7a2da50258c4d0f693b738d3f3c69b2693aea6c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6014dd456b414b305fb0b408404efdcec18e64bc", - "reference": "6014dd456b414b305fb0b408404efdcec18e64bc", + "url": "https://api.github.com/repos/laravel/framework/zipball/7a2da50258c4d0f693b738d3f3c69b2693aea6c1", + "reference": "7a2da50258c4d0f693b738d3f3c69b2693aea6c1", "shasum": "" }, "require": { @@ -4329,7 +4329,7 @@ "symfony/console": "^6.2", "symfony/error-handler": "^6.2", "symfony/finder": "^6.2", - "symfony/http-foundation": "^6.2", + "symfony/http-foundation": "^6.3", "symfony/http-kernel": "^6.2", "symfony/mailer": "^6.2", "symfony/mime": "^6.2", @@ -4396,13 +4396,15 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", + "nyholm/psr7": "^1.2", "orchestra/testbench-core": "^8.12", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", "predis/predis": "^2.0.2", "symfony/cache": "^6.2", - "symfony/http-client": "^6.2.4" + "symfony/http-client": "^6.2.4", + "symfony/psr-http-message-bridge": "^2.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -4483,27 +4485,27 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-09-28T14:08:59+00:00" + "time": "2023-11-01T13:52:17+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.10", + "version": "v0.1.13", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "37ed55f6950d921a87d5beeab16d03f8de26b060" + "reference": "e1379d8ead15edd6cc4369c22274345982edc95a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/37ed55f6950d921a87d5beeab16d03f8de26b060", - "reference": "37ed55f6950d921a87d5beeab16d03f8de26b060", + "url": "https://api.github.com/repos/laravel/prompts/zipball/e1379d8ead15edd6cc4369c22274345982edc95a", + "reference": "e1379d8ead15edd6cc4369c22274345982edc95a", "shasum": "" }, "require": { "ext-mbstring": "*", "illuminate/collections": "^10.0|^11.0", "php": "^8.1", - "symfony/console": "^6.2" + "symfony/console": "^6.2|^7.0" }, "conflict": { "illuminate/console": ">=10.17.0 <10.25.0", @@ -4538,22 +4540,22 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.10" + "source": "https://github.com/laravel/prompts/tree/v0.1.13" }, - "time": "2023-09-29T07:26:07+00:00" + "time": "2023-10-27T13:53:59+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.1", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902" + "reference": "076fe2cf128bd54b4341cdc6d49b95b34e101e4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902", - "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/076fe2cf128bd54b4341cdc6d49b95b34e101e4c", + "reference": "076fe2cf128bd54b4341cdc6d49b95b34e101e4c", "shasum": "" }, "require": { @@ -4600,7 +4602,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-07-14T13:56:28+00:00" + "time": "2023-10-17T13:38:16+00:00" }, { "name": "laravel/slack-notification-channel", @@ -4665,16 +4667,16 @@ }, { "name": "laravel/socialite", - "version": "v5.9.1", + "version": "v5.10.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "49ecc4c907ed88c1254bae991c6b2948945645c2" + "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/49ecc4c907ed88c1254bae991c6b2948945645c2", - "reference": "49ecc4c907ed88c1254bae991c6b2948945645c2", + "url": "https://api.github.com/repos/laravel/socialite/zipball/f376b6eda9084899e37ac08bafd64a95edf9c6c0", + "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0", "shasum": "" }, "require": { @@ -4731,7 +4733,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2023-09-07T16:13:53+00:00" + "time": "2023-10-30T22:09:58+00:00" }, { "name": "laravel/tinker", @@ -5280,16 +5282,16 @@ }, { "name": "league/flysystem", - "version": "3.16.0", + "version": "3.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729" + "reference": "015633a05aee22490495159237a5944091d8281e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729", - "reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/015633a05aee22490495159237a5944091d8281e", + "reference": "015633a05aee22490495159237a5944091d8281e", "shasum": "" }, "require": { @@ -5307,8 +5309,8 @@ "symfony/http-client": "<5.2" }, "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.1", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", @@ -5318,7 +5320,7 @@ "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", "phpseclib/phpseclib": "^3.0.14", - "phpstan/phpstan": "^0.12.26", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, @@ -5354,7 +5356,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.16.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.18.0" }, "funding": [ { @@ -5366,7 +5368,7 @@ "type": "github" } ], - "time": "2023-09-07T19:22:17+00:00" + "time": "2023-10-20T17:59:40+00:00" }, { "name": "league/flysystem-aws-s3-v3", @@ -5436,16 +5438,16 @@ }, { "name": "league/flysystem-local", - "version": "3.16.0", + "version": "3.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781" + "reference": "e7381ef7643f658b87efb7dbe98fe538fb1bbf32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ec7383f25642e6fd4bb0c9554fc2311245391781", - "reference": "ec7383f25642e6fd4bb0c9554fc2311245391781", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e7381ef7643f658b87efb7dbe98fe538fb1bbf32", + "reference": "e7381ef7643f658b87efb7dbe98fe538fb1bbf32", "shasum": "" }, "require": { @@ -5480,7 +5482,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.16.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.18.0" }, "funding": [ { @@ -5492,7 +5494,7 @@ "type": "github" } ], - "time": "2023-08-30T10:23:59+00:00" + "time": "2023-10-19T20:07:13+00:00" }, { "name": "league/fractal", @@ -5566,16 +5568,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.13.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", - "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e", + "reference": "b6a5854368533df0295c5761a0253656a2e52d9e", "shasum": "" }, "require": { @@ -5606,7 +5608,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0" }, "funding": [ { @@ -5618,7 +5620,7 @@ "type": "tidelift" } ], - "time": "2023-08-05T12:09:49+00:00" + "time": "2023-10-17T14:13:20+00:00" }, { "name": "league/oauth1-client", @@ -5834,16 +5836,16 @@ }, { "name": "microsoft/microsoft-graph", - "version": "1.107.0", + "version": "1.109.0", "source": { "type": "git", "url": "https://github.com/microsoftgraph/msgraph-sdk-php.git", - "reference": "63fed05d4d9c348db094f8d8a1d44ff9ce6887c7" + "reference": "14b1b9f24a6b6ace91323b1121030c96a0988ee0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/63fed05d4d9c348db094f8d8a1d44ff9ce6887c7", - "reference": "63fed05d4d9c348db094f8d8a1d44ff9ce6887c7", + "url": "https://api.github.com/repos/microsoftgraph/msgraph-sdk-php/zipball/14b1b9f24a6b6ace91323b1121030c96a0988ee0", + "reference": "14b1b9f24a6b6ace91323b1121030c96a0988ee0", "shasum": "" }, "require": { @@ -5880,22 +5882,22 @@ "homepage": "https://developer.microsoft.com/en-us/graph", "support": { "issues": "https://github.com/microsoftgraph/msgraph-sdk-php/issues", - "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.107.0" + "source": "https://github.com/microsoftgraph/msgraph-sdk-php/tree/1.109.0" }, - "time": "2023-09-27T06:43:40+00:00" + "time": "2023-11-02T10:25:39+00:00" }, { "name": "mollie/mollie-api-php", - "version": "v2.61.0", + "version": "v2.62.0", "source": { "type": "git", "url": "https://github.com/mollie/mollie-api-php.git", - "reference": "d3ec7a191985aa57bec9b4425a665e95b4ba346a" + "reference": "feb6d52859ed1ea7a65b25bb6cbfaadb04b33827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/d3ec7a191985aa57bec9b4425a665e95b4ba346a", - "reference": "d3ec7a191985aa57bec9b4425a665e95b4ba346a", + "url": "https://api.github.com/repos/mollie/mollie-api-php/zipball/feb6d52859ed1ea7a65b25bb6cbfaadb04b33827", + "reference": "feb6d52859ed1ea7a65b25bb6cbfaadb04b33827", "shasum": "" }, "require": { @@ -5972,9 +5974,9 @@ ], "support": { "issues": "https://github.com/mollie/mollie-api-php/issues", - "source": "https://github.com/mollie/mollie-api-php/tree/v2.61.0" + "source": "https://github.com/mollie/mollie-api-php/tree/v2.62.0" }, - "time": "2023-07-31T15:37:46+00:00" + "time": "2023-10-23T11:22:58+00:00" }, { "name": "moneyphp/money", @@ -6067,16 +6069,16 @@ }, { "name": "monolog/monolog", - "version": "3.4.0", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "e2392369686d420ca32df3803de28b5d6f76867d" + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", - "reference": "e2392369686d420ca32df3803de28b5d6f76867d", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448", "shasum": "" }, "require": { @@ -6152,7 +6154,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.4.0" + "source": "https://github.com/Seldaek/monolog/tree/3.5.0" }, "funding": [ { @@ -6164,7 +6166,7 @@ "type": "tidelift" } ], - "time": "2023-06-21T08:46:11+00:00" + "time": "2023-10-27T15:32:31+00:00" }, { "name": "mtdowling/jmespath.php", @@ -6476,16 +6478,16 @@ }, { "name": "nette/schema", - "version": "v1.2.4", + "version": "v1.2.5", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab" + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab", - "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", + "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", "shasum": "" }, "require": { @@ -6532,22 +6534,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.4" + "source": "https://github.com/nette/schema/tree/v1.2.5" }, - "time": "2023-08-05T18:56:25+00:00" + "time": "2023-10-05T20:37:59+00:00" }, { "name": "nette/utils", - "version": "v4.0.2", + "version": "v4.0.3", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "cead6637226456b35e1175cc53797dd585d85545" + "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/cead6637226456b35e1175cc53797dd585d85545", - "reference": "cead6637226456b35e1175cc53797dd585d85545", + "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", + "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", "shasum": "" }, "require": { @@ -6618,9 +6620,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.2" + "source": "https://github.com/nette/utils/tree/v4.0.3" }, - "time": "2023-09-19T11:58:07+00:00" + "time": "2023-10-29T21:02:13+00:00" }, { "name": "nikic/php-parser", @@ -6766,16 +6768,16 @@ }, { "name": "nwidart/laravel-modules", - "version": "v10.0.0", + "version": "10.0.3", "source": { "type": "git", "url": "https://github.com/nWidart/laravel-modules.git", - "reference": "35e514f13cb8ae8dce093e9794785fea27319d81" + "reference": "786da1e6dfa2df6caa8718acb9c37a8fe94595b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/35e514f13cb8ae8dce093e9794785fea27319d81", - "reference": "35e514f13cb8ae8dce093e9794785fea27319d81", + "url": "https://api.github.com/repos/nWidart/laravel-modules/zipball/786da1e6dfa2df6caa8718acb9c37a8fe94595b3", + "reference": "786da1e6dfa2df6caa8718acb9c37a8fe94595b3", "shasum": "" }, "require": { @@ -6835,15 +6837,19 @@ ], "support": { "issues": "https://github.com/nWidart/laravel-modules/issues", - "source": "https://github.com/nWidart/laravel-modules/tree/v10.0.0" + "source": "https://github.com/nWidart/laravel-modules/tree/10.0.3" }, "funding": [ + { + "url": "https://github.com/dcblogdev", + "type": "github" + }, { "url": "https://github.com/nwidart", "type": "github" } ], - "time": "2023-02-16T11:08:15+00:00" + "time": "2023-11-02T17:47:33+00:00" }, { "name": "nyholm/psr7", @@ -7285,22 +7291,22 @@ }, { "name": "payfast/payfast-php-sdk", - "version": "v1.1.4", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/PayFast/payfast-php-sdk.git", - "reference": "897e88dabc99283e891d6f8dbad25ccca7a787b9" + "url": "https://github.com/Payfast/payfast-php-sdk.git", + "reference": "902b2cfa7318ad947ed0eba953eea4a3831c526a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PayFast/payfast-php-sdk/zipball/897e88dabc99283e891d6f8dbad25ccca7a787b9", - "reference": "897e88dabc99283e891d6f8dbad25ccca7a787b9", + "url": "https://api.github.com/repos/Payfast/payfast-php-sdk/zipball/902b2cfa7318ad947ed0eba953eea4a3831c526a", + "reference": "902b2cfa7318ad947ed0eba953eea4a3831c526a", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/guzzle": ">=6.0.0", - "php": ">=7.2.5" + "php": ">=8.1" }, "require-dev": { "phpunit/phpunit": "^9" @@ -7308,7 +7314,7 @@ "type": "library", "autoload": { "psr-4": { - "PayFast\\": "lib/" + "Payfast\\": "lib/" } }, "notification-url": "https://packagist.org/downloads/", @@ -7317,11 +7323,11 @@ ], "authors": [ { - "name": "Claire Grant", - "email": "claire.grant@payfast.co.za" + "name": "Payfast", + "email": "support@payfast.help" } ], - "description": "PayFast PHP Library", + "description": "Payfast PHP Library", "keywords": [ "api", "onsite", @@ -7329,11 +7335,11 @@ "php" ], "support": { - "issues": "https://github.com/PayFast/payfast-php-sdk/issues", - "source": "https://github.com/PayFast/payfast-php-sdk/tree/v1.1.4" + "issues": "https://github.com/Payfast/payfast-php-sdk/issues", + "source": "https://github.com/Payfast/payfast-php-sdk/tree/v1.1.5" }, "abandoned": true, - "time": "2022-12-20T10:39:51+00:00" + "time": "2023-10-11T09:57:01+00:00" }, { "name": "php-http/client-common", @@ -7727,31 +7733,26 @@ }, { "name": "php-http/promise", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + "reference": "ef4905bfb492ff389eb7f12e26925a0f20073050" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "url": "https://api.github.com/repos/php-http/promise/zipball/ef4905bfb492ff389eb7f12e26925a0f20073050", + "reference": "ef4905bfb492ff389eb7f12e26925a0f20073050", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { "Http\\Promise\\": "src/" @@ -7778,9 +7779,9 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" + "source": "https://github.com/php-http/promise/tree/1.2.0" }, - "time": "2020-07-07T09:29:14+00:00" + "time": "2023-10-24T09:20:26+00:00" }, { "name": "php-jsonpointer/php-jsonpointer", @@ -8026,16 +8027,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.23", + "version": "3.0.33", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "866cc78fbd82462ffd880e3f65692afe928bed50" + "reference": "33fa69b2514a61138dd48e7a49f99445711e0ad0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/866cc78fbd82462ffd880e3f65692afe928bed50", - "reference": "866cc78fbd82462ffd880e3f65692afe928bed50", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/33fa69b2514a61138dd48e7a49f99445711e0ad0", + "reference": "33fa69b2514a61138dd48e7a49f99445711e0ad0", "shasum": "" }, "require": { @@ -8116,7 +8117,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.23" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.33" }, "funding": [ { @@ -8132,7 +8133,7 @@ "type": "tidelift" } ], - "time": "2023-09-18T17:22:01+00:00" + "time": "2023-10-21T14:00:39+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -8757,16 +8758,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.21", + "version": "v0.11.22", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "bcb22101107f3bf770523b65630c9d547f60c540" + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/bcb22101107f3bf770523b65630c9d547f60c540", - "reference": "bcb22101107f3bf770523b65630c9d547f60c540", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/128fa1b608be651999ed9789c95e6e2a31b5802b", + "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b", "shasum": "" }, "require": { @@ -8795,7 +8796,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.11.x-dev" + "dev-0.11": "0.11.x-dev" }, "bamarni-bin": { "bin-links": false, @@ -8831,9 +8832,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.21" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.22" }, - "time": "2023-09-17T21:15:54+00:00" + "time": "2023-10-14T21:56:36+00:00" }, { "name": "pusher/pusher-php-server", @@ -9460,16 +9461,16 @@ }, { "name": "sentry/sentry", - "version": "3.21.0", + "version": "3.22.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "624aafc22b84b089ffa43b71fb01e0096505ec4f" + "reference": "c0e3df5a5c1d133cd9461e7672568ff07042c19d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/624aafc22b84b089ffa43b71fb01e0096505ec4f", - "reference": "624aafc22b84b089ffa43b71fb01e0096505ec4f", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/c0e3df5a5c1d133cd9461e7672568ff07042c19d", + "reference": "c0e3df5a5c1d133cd9461e7672568ff07042c19d", "shasum": "" }, "require": { @@ -9487,7 +9488,7 @@ "psr/http-factory": "^1.0", "psr/http-factory-implementation": "^1.0", "psr/log": "^1.0|^2.0|^3.0", - "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0", "symfony/polyfill-php80": "^1.17" }, "conflict": { @@ -9544,7 +9545,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/3.21.0" + "source": "https://github.com/getsentry/sentry-php/tree/3.22.0" }, "funding": [ { @@ -9556,20 +9557,20 @@ "type": "custom" } ], - "time": "2023-07-31T15:31:24+00:00" + "time": "2023-10-23T20:34:53+00:00" }, { "name": "sentry/sentry-laravel", - "version": "3.8.0", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "c7e7611553f9f90af10ed98dde1a680220f02e4d" + "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/c7e7611553f9f90af10ed98dde1a680220f02e4d", - "reference": "c7e7611553f9f90af10ed98dde1a680220f02e4d", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/1293e5732f8405e12f000cdf5dee78c927a18de0", + "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0", "shasum": "" }, "require": { @@ -9636,7 +9637,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.2" }, "funding": [ { @@ -9648,7 +9649,7 @@ "type": "custom" } ], - "time": "2023-09-05T11:02:34+00:00" + "time": "2023-10-12T14:38:46+00:00" }, { "name": "setasign/fpdf", @@ -10080,16 +10081,16 @@ }, { "name": "spatie/laravel-data", - "version": "3.9.0", + "version": "3.9.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "21bad55113a1e1e5180a0f89b695f02ce1732aef" + "reference": "dea1e755549a9b54f5895097cc56b9f244f97867" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/21bad55113a1e1e5180a0f89b695f02ce1732aef", - "reference": "21bad55113a1e1e5180a0f89b695f02ce1732aef", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/dea1e755549a9b54f5895097cc56b9f244f97867", + "reference": "dea1e755549a9b54f5895097cc56b9f244f97867", "shasum": "" }, "require": { @@ -10151,7 +10152,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/3.9.0" + "source": "https://github.com/spatie/laravel-data/tree/3.9.2" }, "funding": [ { @@ -10159,7 +10160,7 @@ "type": "github" } ], - "time": "2023-09-15T12:04:39+00:00" + "time": "2023-10-20T10:18:36+00:00" }, { "name": "spatie/laravel-package-tools", @@ -10978,16 +10979,16 @@ }, { "name": "symfony/http-client", - "version": "v6.3.5", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d" + "reference": "cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/213e564da4cbf61acc9728d97e666bcdb868c10d", - "reference": "213e564da4cbf61acc9728d97e666bcdb868c10d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d", + "reference": "cd67fcaf4524ec6ae5d9b2d9497682d7ad3ce57d", "shasum": "" }, "require": { @@ -11050,7 +11051,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.5" + "source": "https://github.com/symfony/http-client/tree/v6.3.7" }, "funding": [ { @@ -11066,7 +11067,7 @@ "type": "tidelift" } ], - "time": "2023-09-29T15:57:12+00:00" + "time": "2023-10-29T12:41:36+00:00" }, { "name": "symfony/http-client-contracts", @@ -11148,16 +11149,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.3.5", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957" + "reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b50f5e281d722cb0f4c296f908bacc3e2b721957", - "reference": "b50f5e281d722cb0f4c296f908bacc3e2b721957", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/59d1837d5d992d16c2628cd0d6b76acf8d69b33e", + "reference": "59d1837d5d992d16c2628cd0d6b76acf8d69b33e", "shasum": "" }, "require": { @@ -11167,12 +11168,12 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.2" + "symfony/cache": "<6.3" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3.0", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^5.4|^6.0", + "symfony/cache": "^6.3", "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", @@ -11205,7 +11206,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.5" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.7" }, "funding": [ { @@ -11221,20 +11222,20 @@ "type": "tidelift" } ], - "time": "2023-09-04T21:33:54+00:00" + "time": "2023-10-28T23:55:27+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.5", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc" + "reference": "6d4098095f93279d9536a0e9124439560cc764d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f991a964368bee8d883e8d57ced4fe9fff04dfc", - "reference": "9f991a964368bee8d883e8d57ced4fe9fff04dfc", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6d4098095f93279d9536a0e9124439560cc764d0", + "reference": "6d4098095f93279d9536a0e9124439560cc764d0", "shasum": "" }, "require": { @@ -11318,7 +11319,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.3.5" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.7" }, "funding": [ { @@ -11334,20 +11335,20 @@ "type": "tidelift" } ], - "time": "2023-09-30T06:37:04+00:00" + "time": "2023-10-29T14:31:45+00:00" }, { "name": "symfony/intl", - "version": "v6.3.2", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69" + "reference": "4cc98c05f2c55150a6aa5b3e20667f7a6d06cca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", - "reference": "1f8cb145c869ed089a8531c51a6a4b31ed0b3c69", + "url": "https://api.github.com/repos/symfony/intl/zipball/4cc98c05f2c55150a6aa5b3e20667f7a6d06cca9", + "reference": "4cc98c05f2c55150a6aa5b3e20667f7a6d06cca9", "shasum": "" }, "require": { @@ -11400,7 +11401,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.3.2" + "source": "https://github.com/symfony/intl/tree/v6.3.7" }, "funding": [ { @@ -11416,7 +11417,7 @@ "type": "tidelift" } ], - "time": "2023-07-20T07:43:09+00:00" + "time": "2023-10-28T23:11:45+00:00" }, { "name": "symfony/mailer", @@ -11500,16 +11501,16 @@ }, { "name": "symfony/mailgun-mailer", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "b467aba49c8240a71f7027c213d9d140ba1abce7" + "reference": "8d9741467c53750dc8ccda23a1cdb91cda732571" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/b467aba49c8240a71f7027c213d9d140ba1abce7", - "reference": "b467aba49c8240a71f7027c213d9d140ba1abce7", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/8d9741467c53750dc8ccda23a1cdb91cda732571", + "reference": "8d9741467c53750dc8ccda23a1cdb91cda732571", "shasum": "" }, "require": { @@ -11549,7 +11550,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.5" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.3.6" }, "funding": [ { @@ -11565,7 +11566,7 @@ "type": "tidelift" } ], - "time": "2023-09-29T17:30:10+00:00" + "time": "2023-10-12T13:32:47+00:00" }, { "name": "symfony/mime", @@ -13261,16 +13262,16 @@ }, { "name": "symfony/translation", - "version": "v6.3.3", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd" + "reference": "30212e7c87dcb79c83f6362b00bde0e0b1213499" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", - "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", + "url": "https://api.github.com/repos/symfony/translation/zipball/30212e7c87dcb79c83f6362b00bde0e0b1213499", + "reference": "30212e7c87dcb79c83f6362b00bde0e0b1213499", "shasum": "" }, "require": { @@ -13336,7 +13337,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.3.3" + "source": "https://github.com/symfony/translation/tree/v6.3.7" }, "funding": [ { @@ -13352,7 +13353,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-10-28T23:11:45+00:00" }, { "name": "symfony/translation-contracts", @@ -13508,16 +13509,16 @@ }, { "name": "symfony/validator", - "version": "v6.3.5", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47" + "reference": "9cc736663fa5839b9710ac2c303bb0b951014fc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", - "reference": "48e815ba3b5eb72e632588dbf7ea2dc4e608ee47", + "url": "https://api.github.com/repos/symfony/validator/zipball/9cc736663fa5839b9710ac2c303bb0b951014fc1", + "reference": "9cc736663fa5839b9710ac2c303bb0b951014fc1", "shasum": "" }, "require": { @@ -13584,7 +13585,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v6.3.5" + "source": "https://github.com/symfony/validator/tree/v6.3.7" }, "funding": [ { @@ -13600,20 +13601,20 @@ "type": "tidelift" } ], - "time": "2023-09-29T07:41:15+00:00" + "time": "2023-10-28T23:11:45+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.3.5", + "version": "v6.3.6", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5" + "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3d9999376be5fea8de47752837a3e1d1c5f69ef5", - "reference": "3d9999376be5fea8de47752837a3e1d1c5f69ef5", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/999ede244507c32b8e43aebaa10e9fce20de7c97", + "reference": "999ede244507c32b8e43aebaa10e9fce20de7c97", "shasum": "" }, "require": { @@ -13668,7 +13669,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.5" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.6" }, "funding": [ { @@ -13684,20 +13685,20 @@ "type": "tidelift" } ], - "time": "2023-09-12T10:11:35+00:00" + "time": "2023-10-12T18:45:56+00:00" }, { "name": "symfony/yaml", - "version": "v6.3.3", + "version": "v6.3.7", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add" + "reference": "9758b6c69d179936435d0ffb577c3708d57e38a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e23292e8c07c85b971b44c1c4b87af52133e2add", - "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add", + "url": "https://api.github.com/repos/symfony/yaml/zipball/9758b6c69d179936435d0ffb577c3708d57e38a8", + "reference": "9758b6c69d179936435d0ffb577c3708d57e38a8", "shasum": "" }, "require": { @@ -13740,7 +13741,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.3.3" + "source": "https://github.com/symfony/yaml/tree/v6.3.7" }, "funding": [ { @@ -13756,7 +13757,7 @@ "type": "tidelift" } ], - "time": "2023-07-31T07:08:24+00:00" + "time": "2023-10-28T23:31:00+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -14663,16 +14664,16 @@ }, { "name": "brianium/paratest", - "version": "v7.2.7", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "1526eb4fd195f65075456dee394d14742ae0a66c" + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/1526eb4fd195f65075456dee394d14742ae0a66c", - "reference": "1526eb4fd195f65075456dee394d14742ae0a66c", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/551f46f52a93177d873f3be08a1649ae886b4a30", + "reference": "551f46f52a93177d873f3be08a1649ae886b4a30", "shasum": "" }, "require": { @@ -14680,28 +14681,28 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "fidry/cpu-core-counter": "^0.5.1 || ^1.0.0", "jean85/pretty-package-versions": "^2.0.5", "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "phpunit/php-code-coverage": "^10.1.3", - "phpunit/php-file-iterator": "^4.0.2", + "phpunit/php-code-coverage": "^10.1.7", + "phpunit/php-file-iterator": "^4.1.0", "phpunit/php-timer": "^6.0", - "phpunit/phpunit": "^10.3.2", + "phpunit/phpunit": "^10.4.2", "sebastian/environment": "^6.0.1", - "symfony/console": "^6.3.4", - "symfony/process": "^6.3.4" + "symfony/console": "^6.3.4 || ^7.0.0", + "symfony/process": "^6.3.4 || ^7.0.0" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "infection/infection": "^0.27.0", - "phpstan/phpstan": "^1.10.32", + "infection/infection": "^0.27.6", + "phpstan/phpstan": "^1.10.40", "phpstan/phpstan-deprecation-rules": "^1.1.4", - "phpstan/phpstan-phpunit": "^1.3.14", - "phpstan/phpstan-strict-rules": "^1.5.1", + "phpstan/phpstan-phpunit": "^1.3.15", + "phpstan/phpstan-strict-rules": "^1.5.2", "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^6.3.1" + "symfony/filesystem": "^6.3.1 || ^7.0.0" }, "bin": [ "bin/paratest", @@ -14742,7 +14743,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.2.7" + "source": "https://github.com/paratestphp/paratest/tree/v7.3.1" }, "funding": [ { @@ -14754,7 +14755,7 @@ "type": "paypal" } ], - "time": "2023-09-14T14:10:09+00:00" + "time": "2023-10-31T09:24:17+00:00" }, { "name": "composer/class-map-generator", @@ -14831,16 +14832,16 @@ }, { "name": "composer/pcre", - "version": "3.1.0", + "version": "3.1.1", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", - "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", "shasum": "" }, "require": { @@ -14882,7 +14883,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.0" + "source": "https://github.com/composer/pcre/tree/3.1.1" }, "funding": [ { @@ -14898,7 +14899,7 @@ "type": "tidelift" } ], - "time": "2022-11-17T09:50:14+00:00" + "time": "2023-10-11T07:11:09+00:00" }, { "name": "composer/semver", @@ -15049,16 +15050,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "0.5.1", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + "reference": "85193c0b0cb5c47894b5eaec906e946f054e7077" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", - "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/85193c0b0cb5c47894b5eaec906e946f054e7077", + "reference": "85193c0b0cb5c47894b5eaec906e946f054e7077", "shasum": "" }, "require": { @@ -15066,13 +15067,13 @@ }, "require-dev": { "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", "phpstan/extension-installer": "^1.2.0", "phpstan/phpstan": "^1.9.2", "phpstan/phpstan-deprecation-rules": "^1.0.0", "phpstan/phpstan-phpunit": "^1.2.2", "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^9.5.26 || ^8.5.31", - "theofidry/php-cs-fixer-config": "^1.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", "webmozarts/strict-phpunit": "^7.5" }, "type": "library", @@ -15098,7 +15099,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.0.0" }, "funding": [ { @@ -15106,20 +15107,20 @@ "type": "github" } ], - "time": "2022-12-24T12:35:10+00:00" + "time": "2023-09-17T21:38:23+00:00" }, { "name": "filp/whoops", - "version": "2.15.3", + "version": "2.15.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "c83e88a30524f9360b11f585f71e6b17313b7187" + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/c83e88a30524f9360b11f585f71e6b17313b7187", - "reference": "c83e88a30524f9360b11f585f71e6b17313b7187", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", "shasum": "" }, "require": { @@ -15169,7 +15170,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.3" + "source": "https://github.com/filp/whoops/tree/2.15.4" }, "funding": [ { @@ -15177,20 +15178,20 @@ "type": "github" } ], - "time": "2023-07-13T12:00:00+00:00" + "time": "2023-11-03T12:00:00+00:00" }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.34.0", + "version": "v3.37.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7c7a4ad2ed8fe50df3e25528218b13d383608f23" + "reference": "c3fe76976081ab871aa654e872da588077e19679" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7c7a4ad2ed8fe50df3e25528218b13d383608f23", - "reference": "7c7a4ad2ed8fe50df3e25528218b13d383608f23", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/c3fe76976081ab871aa654e872da588077e19679", + "reference": "c3fe76976081ab871aa654e872da588077e19679", "shasum": "" }, "require": { @@ -15211,9 +15212,6 @@ "symfony/process": "^5.4 || ^6.0", "symfony/stopwatch": "^5.4 || ^6.0" }, - "conflict": { - "stevebauman/unfinalize": "*" - }, "require-dev": { "facile-it/paraunit": "^1.3 || ^2.0", "justinrainbow/json-schema": "^5.2", @@ -15226,8 +15224,6 @@ "phpspec/prophecy": "^1.16", "phpspec/prophecy-phpunit": "^2.0", "phpunit/phpunit": "^9.5", - "phpunitgoodpractices/polyfill": "^1.6", - "phpunitgoodpractices/traits": "^1.9.2", "symfony/phpunit-bridge": "^6.2.3", "symfony/yaml": "^5.4 || ^6.0" }, @@ -15267,7 +15263,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.34.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.37.1" }, "funding": [ { @@ -15275,7 +15271,7 @@ "type": "github" } ], - "time": "2023-09-29T15:34:26+00:00" + "time": "2023-10-29T20:51:23+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -15389,16 +15385,16 @@ }, { "name": "maximebf/debugbar", - "version": "v1.19.0", + "version": "v1.19.1", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "30f65f18f7ac086255a77a079f8e0dcdd35e828e" + "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30f65f18f7ac086255a77a079f8e0dcdd35e828e", - "reference": "30f65f18f7ac086255a77a079f8e0dcdd35e828e", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/03dd40a1826f4d585ef93ef83afa2a9874a00523", + "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523", "shasum": "" }, "require": { @@ -15449,9 +15445,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.0" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.1" }, - "time": "2023-09-19T19:53:10+00:00" + "time": "2023-10-12T08:10:52+00:00" }, { "name": "mockery/mockery", @@ -15599,16 +15595,16 @@ }, { "name": "nunomaduro/collision", - "version": "v7.9.0", + "version": "v7.10.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "296d0cf9fe462837ac0da8a568b56fc026b132da" + "reference": "49ec67fa7b002712da8526678abd651c09f375b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/296d0cf9fe462837ac0da8a568b56fc026b132da", - "reference": "296d0cf9fe462837ac0da8a568b56fc026b132da", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/49ec67fa7b002712da8526678abd651c09f375b2", + "reference": "49ec67fa7b002712da8526678abd651c09f375b2", "shasum": "" }, "require": { @@ -15617,19 +15613,22 @@ "php": "^8.1.0", "symfony/console": "^6.3.4" }, + "conflict": { + "laravel/framework": ">=11.0.0" + }, "require-dev": { - "brianium/paratest": "^7.2.7", - "laravel/framework": "^10.23.1", - "laravel/pint": "^1.13.1", + "brianium/paratest": "^7.3.0", + "laravel/framework": "^10.28.0", + "laravel/pint": "^1.13.3", "laravel/sail": "^1.25.0", "laravel/sanctum": "^3.3.1", "laravel/tinker": "^2.8.2", "nunomaduro/larastan": "^2.6.4", - "orchestra/testbench-core": "^8.11.0", - "pestphp/pest": "^2.19.1", - "phpunit/phpunit": "^10.3.5", + "orchestra/testbench-core": "^8.13.0", + "pestphp/pest": "^2.23.2", + "phpunit/phpunit": "^10.4.1", "sebastian/environment": "^6.0.1", - "spatie/laravel-ignition": "^2.3.0" + "spatie/laravel-ignition": "^2.3.1" }, "type": "library", "extra": { @@ -15688,7 +15687,7 @@ "type": "patreon" } ], - "time": "2023-09-19T10:45:09+00:00" + "time": "2023-10-11T15:45:01+00:00" }, { "name": "nunomaduro/larastan", @@ -15986,16 +15985,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.36", + "version": "1.10.41", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ffa3089511121a672e62969404e4fddc753f9b15" + "reference": "c6174523c2a69231df55bdc65b61655e72876d76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa3089511121a672e62969404e4fddc753f9b15", - "reference": "ffa3089511121a672e62969404e4fddc753f9b15", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6174523c2a69231df55bdc65b61655e72876d76", + "reference": "c6174523c2a69231df55bdc65b61655e72876d76", "shasum": "" }, "require": { @@ -16044,20 +16043,20 @@ "type": "tidelift" } ], - "time": "2023-09-29T14:07:45+00:00" + "time": "2023-11-05T12:57:57+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.6", + "version": "10.1.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "56f33548fe522c8d82da7ff3824b42829d324364" + "reference": "355324ca4980b8916c18b9db29f3ef484078f26e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/56f33548fe522c8d82da7ff3824b42829d324364", - "reference": "56f33548fe522c8d82da7ff3824b42829d324364", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e", + "reference": "355324ca4980b8916c18b9db29f3ef484078f26e", "shasum": "" }, "require": { @@ -16114,7 +16113,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7" }, "funding": [ { @@ -16122,7 +16121,7 @@ "type": "github" } ], - "time": "2023-09-19T04:59:03+00:00" + "time": "2023-10-04T15:34:17+00:00" }, { "name": "phpunit/php-file-iterator", @@ -16369,16 +16368,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.3.5", + "version": "10.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503" + "reference": "cacd8b9dd224efa8eb28beb69004126c7ca1a1a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/747c3b2038f1139e3dcd9886a3f5a948648b7503", - "reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/cacd8b9dd224efa8eb28beb69004126c7ca1a1a1", + "reference": "cacd8b9dd224efa8eb28beb69004126c7ca1a1a1", "shasum": "" }, "require": { @@ -16418,7 +16417,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.3-dev" + "dev-main": "10.4-dev" } }, "autoload": { @@ -16450,7 +16449,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.4.2" }, "funding": [ { @@ -16466,7 +16465,7 @@ "type": "tidelift" } ], - "time": "2023-09-19T05:42:37+00:00" + "time": "2023-10-26T07:21:45+00:00" }, { "name": "sebastian/cli-parser", @@ -17447,35 +17446,35 @@ }, { "name": "spatie/flare-client-php", - "version": "1.4.2", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "5f2c6a7a0d2c1d90c12559dc7828fd942911a544" + "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5f2c6a7a0d2c1d90c12559dc7828fd942911a544", - "reference": "5f2c6a7a0d2c1d90c12559dc7828fd942911a544", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", + "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", "nesbot/carbon": "^2.62.1", "php": "^8.0", "spatie/backtrace": "^1.5.2", - "symfony/http-foundation": "^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", - "symfony/process": "^5.2|^6.0", - "symfony/var-dumper": "^5.2|^6.0" + "symfony/http-foundation": "^5.2|^6.0|^7.0", + "symfony/mime": "^5.2|^6.0|^7.0", + "symfony/process": "^5.2|^6.0|^7.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0" }, "require-dev": { - "dms/phpunit-arraysubset-asserts": "^0.3.0", - "pestphp/pest": "^1.20", + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0" + "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" }, "type": "library", "extra": { @@ -17505,7 +17504,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.4.2" + "source": "https://github.com/spatie/flare-client-php/tree/1.4.3" }, "funding": [ { @@ -17513,20 +17512,20 @@ "type": "github" } ], - "time": "2023-07-28T08:07:24+00:00" + "time": "2023-10-17T15:54:07+00:00" }, { "name": "spatie/ignition", - "version": "1.11.2", + "version": "1.11.3", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "48b23411ca4bfbc75c75dfc638b6b36159c375aa" + "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/48b23411ca4bfbc75c75dfc638b6b36159c375aa", - "reference": "48b23411ca4bfbc75c75dfc638b6b36159c375aa", + "url": "https://api.github.com/repos/spatie/ignition/zipball/3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", + "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", "shasum": "" }, "require": { @@ -17535,19 +17534,19 @@ "php": "^8.0", "spatie/backtrace": "^1.5.3", "spatie/flare-client-php": "^1.4.0", - "symfony/console": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "illuminate/cache": "^9.52", + "illuminate/cache": "^9.52|^10.0|^11.0", "mockery/mockery": "^1.4", - "pestphp/pest": "^1.20", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "psr/simple-cache-implementation": "*", - "symfony/cache": "^6.0", - "symfony/process": "^5.4|^6.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -17596,20 +17595,20 @@ "type": "github" } ], - "time": "2023-09-19T15:29:52+00:00" + "time": "2023-10-18T14:09:40+00:00" }, { "name": "spatie/laravel-ignition", - "version": "2.3.0", + "version": "2.3.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "4ed813d16edb5a1ab0d7f4b1d116c37ee8cdf3c0" + "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/4ed813d16edb5a1ab0d7f4b1d116c37ee8cdf3c0", - "reference": "4ed813d16edb5a1ab0d7f4b1d116c37ee8cdf3c0", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/bf21cd15aa47fa4ec5d73bbc932005c70261efc8", + "reference": "bf21cd15aa47fa4ec5d73bbc932005c70261efc8", "shasum": "" }, "require": { @@ -17688,20 +17687,20 @@ "type": "github" } ], - "time": "2023-08-23T06:24:34+00:00" + "time": "2023-10-09T12:55:26+00:00" }, { "name": "spaze/phpstan-stripe", - "version": "v3.0.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/spaze/phpstan-stripe.git", - "reference": "6e5debe4f65b15192a28bd01f01670ced8250443" + "reference": "ebe8d3a7ae99f45ec024767453a09c93dedec030" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spaze/phpstan-stripe/zipball/6e5debe4f65b15192a28bd01f01670ced8250443", - "reference": "6e5debe4f65b15192a28bd01f01670ced8250443", + "url": "https://api.github.com/repos/spaze/phpstan-stripe/zipball/ebe8d3a7ae99f45ec024767453a09c93dedec030", + "reference": "ebe8d3a7ae99f45ec024767453a09c93dedec030", "shasum": "" }, "require": { @@ -17748,9 +17747,9 @@ ], "support": { "issues": "https://github.com/spaze/phpstan-stripe/issues", - "source": "https://github.com/spaze/phpstan-stripe/tree/v3.0.0" + "source": "https://github.com/spaze/phpstan-stripe/tree/v3.1.0" }, - "time": "2023-03-06T00:39:29+00:00" + "time": "2023-10-28T14:16:00+00:00" }, { "name": "symfony/polyfill-php81", diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php index 2268891fadb9..e771f1416a3f 100644 --- a/tests/Feature/Template/TemplateTest.php +++ b/tests/Feature/Template/TemplateTest.php @@ -24,6 +24,7 @@ use App\Utils\Traits\MakesDates; use App\Services\PdfMaker\PdfMaker; use Illuminate\Support\Facades\App; use App\Jobs\Entity\CreateEntityPdf; +use App\Services\Template\TemplateMock; use App\Services\Template\TemplateService; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; @@ -165,7 +166,7 @@ class TemplateTest extends TestCase '; - private string $stack = '
'; + private string $stack = '
'; protected function setUp() :void { @@ -179,6 +180,32 @@ class TemplateTest extends TestCase } + public function testNegativeDivAttribute() + { + $dom = new \DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($this->stack, 'HTML-ENTITIES', 'UTF-8')); + + $node = $dom->getElementById('company-details'); + $x = $node->getAttribute('nonexistentattribute'); + + $this->assertEquals('', $x); + + } + + public function testStackResolutionWithLabels() + { + + $dom = new \DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($this->stack, 'HTML-ENTITIES', 'UTF-8')); + + $node = $dom->getElementById('company-details'); + $x = $node->getAttribute('labels'); + + $this->assertEquals('true', $x); + + } + + public function testStackResolution() { @@ -187,13 +214,21 @@ class TemplateTest extends TestCase $partials['design']['body'] = $this->stack; $partials['design']['footer'] = ''; + $tm = new TemplateMock($this->company); + $tm->init(); + + $variables = $tm->variables[0]; + $ts = new TemplateService(); $x = $ts->setTemplate($partials) ->setCompany($this->company) - ->parseGlobalStacks() - ->getHtml(); + ->overrideVariables($variables) + ->parseGlobalStacks() + ->parseVariables() + ->getHtml(); + + $this->assertIsString($x); - nlog($x); } public function testDataMaps() From 5eb58804e4ac546d87eab5ffb55746c8b163d50d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 15:54:51 +1100 Subject: [PATCH 05/14] Optionally add labels to field stacks --- app/Services/Template/TemplateService.php | 32 ++++++++++++++--------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index bf1b62507d37..bc2558b91f8c 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -827,7 +827,8 @@ class TemplateService ]; collect($stacks)->filter(function ($stack) { - return $this->document->getElementById($stack) ?? false; + $exists = $this->document->getElementById($stack) ?? false; + return $exists ? ['stack' => $stack, 'labels' => $exists->getAttribute('labels')] : false; })->each(function ($stack){ $this->parseStack($stack); }); @@ -845,13 +846,13 @@ class TemplateService private function parseStack(string $stack): self { - match($stack){ - 'entity-details' => $this->entityDetails(), - 'client-details' => $this->clientDetails(), - 'vendor-details' => $this->vendorDetails(), - 'company-details' => $this->companyDetails(), - 'company-address' => $this->companyAddress(), - 'shipping-details' => $this->shippingDetails(), + match($stack['stack']){ + 'entity-details' => $this->entityDetails($stack['labels'] == 'true'), + 'client-details' => $this->clientDetails($stack['labels'] == 'true'), + 'vendor-details' => $this->vendorDetails($stack['labels'] == 'true'), + 'company-details' => $this->companyDetails($stack['labels'] == 'true'), + 'company-address' => $this->companyAddress($stack['labels'] == 'true'), + 'shipping-details' => $this->shippingDetails($stack['labels'] == 'true'), }; $this->save(); @@ -859,7 +860,7 @@ class TemplateService return $this; } - private function companyDetails(): self + private function companyDetails(bool $include_labels): self { $var_set = $this->getVarSet(); @@ -872,7 +873,8 @@ class TemplateService return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; })->toArray(); - + $company_details = $include_labels ? $this->labelledFieldStack($company_details) : $company_details; + $this->updateElementProperties('company-details', $company_details); return $this; @@ -955,7 +957,7 @@ class TemplateService ($this->entity instanceof Quote) => $entity_string = 'quote', ($this->entity instanceof Credit) => $entity_string = 'credit', ($this->entity instanceof RecurringInvoice) => $entity_string = 'invoice', - ($this->entity instanceof PurchaseOrder) => $entity_string = 'task', + ($this->entity instanceof PurchaseOrder) => $entity_string = 'purchase_order', default => $entity_string = 'invoice', }; @@ -994,7 +996,13 @@ class TemplateService return $this; } - + + /** + * Generates the field stacks with labels + * + * @param array $variables + * @return array + */ private function labelledFieldStack(array $variables): array { From fe61da24ff6834adce39ebb153fa72775cd66767 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 16:22:45 +1100 Subject: [PATCH 06/14] Cleanup for parsing field stacks --- app/Services/Template/TemplateService.php | 287 ++++++++++++++++------ 1 file changed, 206 insertions(+), 81 deletions(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index bc2558b91f8c..13f599d8619c 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -13,6 +13,7 @@ namespace App\Services\Template; use App\Models\Quote; use App\Utils\Number; +use Twig\Error\Error; use App\Models\Client; use App\Models\Credit; use App\Models\Design; @@ -23,8 +24,12 @@ use App\Models\Payment; use App\Models\Project; use App\Utils\HtmlEngine; use League\Fractal\Manager; +use Twig\Error\LoaderError; +use Twig\Error\SyntaxError; +use Twig\Error\RuntimeError; use App\Models\PurchaseOrder; use App\Utils\VendorHtmlEngine; +use Twig\Sandbox\SecurityError; use App\Models\RecurringInvoice; use App\Utils\PaymentHtmlEngine; use App\Utils\Traits\MakesDates; @@ -40,8 +45,9 @@ use League\Fractal\Serializer\ArraySerializer; class TemplateService { - use MakesDates, PdfMaker; - + use MakesDates; + use PdfMaker; + private \DomDocument $document; public \Twig\Environment $twig; @@ -69,7 +75,7 @@ class TemplateService $this->template = $template; $this->init(); } - + /** * Boot Dom Document * @@ -78,7 +84,6 @@ class TemplateService private function init(): self { - $this->commonmark = new CommonMarkConverter([ 'allow_unsafe_links' => false, ]); @@ -96,22 +101,22 @@ class TemplateService $this->twig->addExtension(new \Twig\Extension\DebugExtension()); $function = new \Twig\TwigFunction('img', function ($string, $style = '') { - return ''; + return ''; }); $this->twig->addFunction($function); $filter = new \Twig\TwigFilter('sum', function (array $array, string $column) { return array_sum(array_column($array, $column)); }); - + $this->twig->addFilter($filter); return $this; } - + /** * Iterate through all of the - * ninja nodes + * ninja nodes, and field stacks * * @param array $data - the payload to be passed into the template * @return self @@ -127,7 +132,13 @@ class TemplateService return $this; } - + + /** + * Initialized a set of HTMLEngine variables + * + * @param array | Collection $data + * @return self + */ private function processVariables($data): self { $this->variables = $this->resolveHtmlEngine($data); @@ -135,6 +146,11 @@ class TemplateService return $this; } + /** + * Returns a Mock Template + * + * @return self + */ public function mock(): self { $tm = new TemplateMock($this->company); @@ -151,7 +167,7 @@ class TemplateService return $this; } - + /** * Returns the HTML as string * @@ -161,13 +177,13 @@ class TemplateService { return $this->compiled_html; } - + /** * Returns the PDF string * - * @return mixed + * @return string */ - public function getPdf(): mixed + public function getPdf(): string { if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { @@ -179,7 +195,7 @@ class TemplateService return $pdf; } - + /** * Get the parsed data * @@ -189,11 +205,11 @@ class TemplateService { return $this->data; } - + /** * Process data variables * - * @param mixed $data + * @param array | Collection $data * @return self */ public function processData($data): self @@ -216,24 +232,24 @@ class TemplateService $contents = $this->document->getElementsByTagName('ninja'); foreach ($contents as $content) { - + $template = $content->ownerDocument->saveHTML($content); try { $template = $this->twig->createTemplate(html_entity_decode($template)); - } catch(\Twig\Error\SyntaxError $e) { + } catch(SyntaxError $e) { nlog($e->getMessage()); throw ($e); - } catch(\Twig\Error\Error $e) { - nlog("error = " .$e->getMessage()); + } catch(Error $e) { + nlog("error = " . $e->getMessage()); throw ($e); - } catch(\Twig\Error\RuntimeError $e) { - nlog("runtime = " .$e->getMessage()); + } catch(RuntimeError $e) { + nlog("runtime = " . $e->getMessage()); throw ($e); - } catch(\Twig\Error\LoaderError $e) { + } catch(LoaderError $e) { nlog("loader = " . $e->getMessage()); throw ($e); - } catch(\Twig\Error\SecurityError $e) { + } catch(SecurityError $e) { nlog("security = " . $e->getMessage()); throw ($e); } @@ -242,7 +258,7 @@ class TemplateService $f = $this->document->createDocumentFragment(); $f->appendXML(html_entity_decode($template)); - + $replacements[] = $f; } @@ -256,7 +272,7 @@ class TemplateService return $this; } - + /** * Parses all variables in the document * @@ -279,7 +295,7 @@ class TemplateService return $this; } - + /** * Saves the document and updates the compiled string. * @@ -314,7 +330,7 @@ class TemplateService return $this; } - + /** * Inject the template components * manually @@ -340,12 +356,13 @@ class TemplateService * Resolves the labels and values needed to replace the string * holders in the template. * + * @param array $data * @return array */ private function resolveHtmlEngine(array $data): array { return collect($data)->map(function ($value, $key) { - + $processed = []; if(in_array($key, ['tasks','projects','aging']) || !$value->first()) { @@ -364,13 +381,20 @@ class TemplateService 'aging' => $processed = [], default => $processed = [], }; - + return $processed; })->toArray(); } + /** + * Pre Processes the Data Blocks into + * Twig consumables + * + * @param array | Collection $data + * @return array + */ private function preProcessDataBlocks($data): array { return collect($data)->map(function ($value, $key) { @@ -394,6 +418,12 @@ class TemplateService })->toArray(); } + /** + * Process Invoices into consumable form for Twig templates + * + * @param array | Collection $invoices + * @return array + */ public function processInvoices($invoices): array { $invoices = collect($invoices) @@ -448,7 +478,7 @@ class TemplateService 'custom_surcharge_tax2' => (bool) $invoice->custom_surcharge_tax2, 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, - 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client): (array) [], + 'line_items' => $invoice->line_items ? $this->padLineItems($invoice->line_items, $invoice->client) : (array) [], 'reminder1_sent' => $this->translateDate($invoice->reminder1_sent, $invoice->client->date_format(), $invoice->client->locale()), 'reminder2_sent' => $this->translateDate($invoice->reminder2_sent, $invoice->client->date_format(), $invoice->client->locale()), 'reminder3_sent' => $this->translateDate($invoice->reminder3_sent, $invoice->client->date_format(), $invoice->client->locale()), @@ -472,6 +502,13 @@ class TemplateService } + /** + * Pads Line Items with raw and formatted content + * + * @param array $items + * @param mixed $client + * @return array + */ public function padLineItems(array $items, Client $client): array { return collect($items)->map(function ($item) use ($client) { @@ -484,11 +521,11 @@ class TemplateService $item->product_cost_raw = $item->product_cost ?? 0; $item->cost = Number::formatMoney($item->cost_raw, $client); - + if($item->is_amount_discount) { $item->discount = Number::formatMoney($item->discount_raw, $client); } - + $item->line_total = Number::formatMoney($item->line_total_raw, $client); $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client); $item->tax_amount = Number::formatMoney($item->tax_amount_raw, $client); @@ -499,11 +536,17 @@ class TemplateService })->toArray(); } + /** + * Transforms a Payment into consumable for twig + * + * @param Payment $payment + * @return array + */ private function transformPayment(Payment $payment): array { $data = []; - + $this->payment = $payment; $credits = $payment->credits->map(function ($credit) use ($payment) { @@ -625,6 +668,12 @@ class TemplateService } + /** + * @todo refactor + * + * @param mixed $quotes + * @return array + */ public function processQuotes($quotes): array { $it = new QuoteTransformer(); @@ -638,18 +687,18 @@ class TemplateService $resources['data'][$key]['client'] = $resource['client']['data'] ?? []; $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? []; - + } return $resources['data']; } - + /** * Pushes credits through the appropriate transformer * and builds any required relationships * - * @param mixed $credits + * @param array | Collection $credits * @return array */ public function processCredits($credits): array @@ -658,7 +707,7 @@ class TemplateService ->map(function ($credit) { $this->entity = $credit; - + return [ 'amount' => Number::formatMoney($credit->amount, $credit->client), 'balance' => Number::formatMoney($credit->balance, $credit->client), @@ -699,7 +748,7 @@ class TemplateService 'custom_surcharge_tax2' => (bool) $credit->custom_surcharge_tax2, 'custom_surcharge_tax3' => (bool) $credit->custom_surcharge_tax3, 'custom_surcharge_tax4' => (bool) $credit->custom_surcharge_tax4, - 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client): (array) [], + 'line_items' => $credit->line_items ? $this->padLineItems($credit->line_items, $credit->client) : (array) [], 'reminder1_sent' => $this->translateDate($credit->reminder1_sent, $credit->client->date_format(), $credit->client->locale()), 'reminder2_sent' => $this->translateDate($credit->reminder2_sent, $credit->client->date_format(), $credit->client->locale()), 'reminder3_sent' => $this->translateDate($credit->reminder3_sent, $credit->client->date_format(), $credit->client->locale()), @@ -723,12 +772,10 @@ class TemplateService } - - /** * Pushes payments through the appropriate transformer * - * @param mixed $payments + * @param array | Collection $payments * @return array */ public function processPayments($payments): array @@ -737,12 +784,17 @@ class TemplateService $payments = collect($payments)->map(function ($payment) { return $this->transformPayment($payment); })->toArray(); - - return $payments; + return $payments; } + /** + * @todo refactor + * + * @param mixed $tasks + * @return array + */ public function processTasks($tasks): array { $it = new TaskTransformer(); @@ -757,7 +809,7 @@ class TemplateService $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? []; $resources['data'][$key]['project'] = $resource['project']['data'] ?? []; $resources['data'][$key]['invoice'] = $resource['invoice'] ?? []; - + } return $resources['data']; @@ -765,6 +817,12 @@ class TemplateService } + /** + * @todo refactor + * + * @param mixed $projects + * @return array + */ public function processProjects($projects): array { @@ -778,9 +836,15 @@ class TemplateService } + /** + * @todo refactor + * + * @param mixed $purchase_orders + * @return array + */ public function processPurchaseOrders($purchase_orders): array { - + $it = new PurchaseOrderTransformer(); $it->setDefaultIncludes(['vendor','expense']); $manager = new Manager(); @@ -791,27 +855,45 @@ class TemplateService } + /** + * Set Company + * + * @param mixed $company + * @return self + */ public function setCompany(Company $company): self { $this->company = $company; - + return $this; } + /** + * Get Company + * + * @return Company + */ public function getCompany(): Company { return $this->company; } + /** + * Setter that allows external variables to override the + * resolved ones from this class + * + * @param mixed $variables + * @return self + */ public function overrideVariables($variables): self { $this->variables = $variables; - + return $this; } - + /** - * Parses and finds any stacks to replace + * Parses and finds any field stacks to inject into the DOM Document * * @return self */ @@ -829,14 +911,14 @@ class TemplateService collect($stacks)->filter(function ($stack) { $exists = $this->document->getElementById($stack) ?? false; return $exists ? ['stack' => $stack, 'labels' => $exists->getAttribute('labels')] : false; - })->each(function ($stack){ + })->each(function ($stack) { $this->parseStack($stack); }); return $this; } - + /** * Injects field stacks into Template * @@ -846,7 +928,7 @@ class TemplateService private function parseStack(string $stack): self { - match($stack['stack']){ + match($stack['stack']) { 'entity-details' => $this->entityDetails($stack['labels'] == 'true'), 'client-details' => $this->clientDetails($stack['labels'] == 'true'), 'vendor-details' => $this->vendorDetails($stack['labels'] == 'true'), @@ -860,27 +942,35 @@ class TemplateService return $this; } + /** + * Inject the Company Details into the DOM Document + * + * @param bool $include_labels + * @return self + */ private function companyDetails(bool $include_labels): self { $var_set = $this->getVarSet(); - $company_details = + $company_details = collect($this->company->settings->pdf_variables->company_details) - ->filter(function ($variable) use($var_set) { + ->filter(function ($variable) use ($var_set) { return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); }) - ->map(function ($variable) { - return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + ->when(!$include_labels, function ($collection) { + return $collection->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + }); })->toArray(); $company_details = $include_labels ? $this->labelledFieldStack($company_details) : $company_details; - + $this->updateElementProperties('company-details', $company_details); return $this; } - private function companyAddress(): self + private function companyAddress(bool $include_labels = false): self { $var_set = $this->getVarSet(); @@ -890,19 +980,30 @@ class TemplateService ->filter(function ($variable) use ($var_set) { return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); }) - ->map(function ($variable) { - return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; + ->when(!$include_labels, function ($collection) { + return $collection->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_address-' . substr($variable, 1)]]; + }); })->toArray(); + $company_address = $include_labels ? $this->labelledFieldStack($company_address) : $company_address; + $this->updateElementProperties('company-address', $company_address); return $this; } - private function shippingDetails(): self + /** + * Injects the Shipping Details into the DOM Document + * + * @param bool $include_labels + * @return self + */ + private function shippingDetails(bool $include_labels = false): self { - if(!$this->entity->client) + if(!$this->entity->client) { return $this; + } $this->client = $this->entity->client; @@ -920,7 +1021,7 @@ class TemplateService ]; $shipping_address = - collect($shipping_address)->filter(function ($address){ + collect($shipping_address)->filter(function ($address) { return isset($address['content']) && !empty($address['content']); })->toArray(); @@ -929,7 +1030,13 @@ class TemplateService return $this; } - private function clientDetails(): self + /** + * Injects the Client Details into the DOM Document + * + * @param bool $include_labels + * @return self + */ + private function clientDetails(bool $include_labels = false): self { $var_set = $this->getVarSet(); @@ -938,21 +1045,31 @@ class TemplateService ->filter(function ($variable) use ($var_set) { return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); }) - ->map(function ($variable) { - return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; + ->when(!$include_labels, function ($collection) { + return $collection->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; + }); })->toArray(); + $client_details = $include_labels ? $this->labelledFieldStack($client_details) : $client_details; $this->updateElementProperties('client-details', $client_details); return $this; } + /** + * Resolves the entity. + * + * Only required for resolving the entity-details stack + * + * @return string + */ private function resolveEntity(): string { $entity_string = ''; - match($this->entity){ + match($this->entity) { ($this->entity instanceof Invoice) => $entity_string = 'invoice', ($this->entity instanceof Quote) => $entity_string = 'quote', ($this->entity instanceof Credit) => $entity_string = 'credit', @@ -964,7 +1081,7 @@ class TemplateService return $entity_string; } - + /** * Returns the variable array by first key, if it exists * @@ -974,7 +1091,7 @@ class TemplateService { return array_key_exists(array_key_first($this->variables), $this->variables) ? $this->variables[array_key_first($this->variables)] : $this->variables; } - + /** * Injects the entity details to the DOM document * @@ -996,7 +1113,7 @@ class TemplateService return $this; } - + /** * Generates the field stacks with labels * @@ -1005,7 +1122,7 @@ class TemplateService */ private function labelledFieldStack(array $variables): array { - + $elements = []; foreach ($variables as $variable) { @@ -1028,10 +1145,16 @@ class TemplateService } return $elements; - + } - private function vendorDetails(): self + /** + * Inject Vendor Details into DOM Document + * + * @param bool $include_labels + * @return self + */ + private function vendorDetails(bool $include_labels = false): self { $var_set = $this->getVarSet(); @@ -1040,11 +1163,13 @@ class TemplateService collect($this->company->settings->pdf_variables->vendor_details) ->filter(function ($variable) use ($var_set) { return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); - }) - ->map(function ($variable) { - return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + })->when(!$include_labels, function ($collection) { + return $collection->map(function ($variable) { + return ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'vendor_details-' . substr($variable, 1)]]; + }); })->toArray(); + $vendor_details = $include_labels ? $this->labelledFieldStack($vendor_details) : $vendor_details; $this->updateElementProperties('vendor-details', $vendor_details); @@ -1096,8 +1221,8 @@ class TemplateService public function updateElementProperties(string $element_id, array $elements): self { $node = $this->document->getElementById($element_id); - - $this->createElementContent($node, $elements); + + $this->createElementContent($node, $elements); return $this; } @@ -1119,7 +1244,7 @@ class TemplateService } - public function createElementContent($element, $children) :self + public function createElementContent($element, $children): self { foreach ($children as $child) { @@ -1140,7 +1265,7 @@ class TemplateService $contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0; } - + if ($contains_html) { // If the element contains the HTML, we gonna display it as is. Backend is going to // encode it for us, preventing any errors on the processing stage. From fbbf4de51f202bd988d093719c8c22db8cae2fb0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 16:46:07 +1100 Subject: [PATCH 07/14] Optional show/hide labels on field stacks --- app/Services/Template/TemplateService.php | 45 ++++++++++++++--------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 13f599d8619c..ef5ad5791618 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -909,9 +909,14 @@ class TemplateService ]; collect($stacks)->filter(function ($stack) { - $exists = $this->document->getElementById($stack) ?? false; - return $exists ? ['stack' => $stack, 'labels' => $exists->getAttribute('labels')] : false; - })->each(function ($stack) { + return $this->document->getElementById($stack) ?? false; + }) + ->map(function ($stack){ + $node = $this->document->getElementById($stack); + nlog(['stack' => $stack, 'labels' => $node->getAttribute('labels')]); + return ['stack' => $stack, 'labels' => $node->getAttribute('labels')]; + }) + ->each(function ($stack) { $this->parseStack($stack); }); @@ -922,10 +927,10 @@ class TemplateService /** * Injects field stacks into Template * - * @param string $stack + * @param array $stack * @return self */ - private function parseStack(string $stack): self + private function parseStack(array $stack): self { match($stack['stack']) { @@ -963,7 +968,11 @@ class TemplateService }); })->toArray(); - $company_details = $include_labels ? $this->labelledFieldStack($company_details) : $company_details; + nlog($company_details); + + $company_details = $include_labels ? $this->labelledFieldStack($company_details, 'company_details-') : $company_details; + + nlog($company_details); $this->updateElementProperties('company-details', $company_details); @@ -986,7 +995,7 @@ class TemplateService }); })->toArray(); - $company_address = $include_labels ? $this->labelledFieldStack($company_address) : $company_address; + $company_address = $include_labels ? $this->labelledFieldStack($company_address, 'company_address-') : $company_address; $this->updateElementProperties('company-address', $company_address); @@ -1051,7 +1060,7 @@ class TemplateService }); })->toArray(); - $client_details = $include_labels ? $this->labelledFieldStack($client_details) : $client_details; + $client_details = $include_labels ? $this->labelledFieldStack($client_details, 'client_details-') : $client_details; $this->updateElementProperties('client-details', $client_details); @@ -1109,7 +1118,7 @@ class TemplateService return isset($var_set['values'][$variable]) && !empty($var_set['values'][$variable]); })->toArray(); - $this->updateElementProperties("entity-details", $this->labelledFieldStack($entity_details)); + $this->updateElementProperties("entity-details", $this->labelledFieldStack($entity_details, 'entity_details-')); return $this; } @@ -1120,7 +1129,7 @@ class TemplateService * @param array $variables * @return array */ - private function labelledFieldStack(array $variables): array + private function labelledFieldStack(array $variables, string $data_ref): array { $elements = []; @@ -1131,15 +1140,17 @@ class TemplateService $var = str_replace("custom", "custom_value", $_variable); + $hidden_prop = ($data_ref == 'entity_details-') ? $this->entityVariableCheck($variable) : false; + if (in_array($_variable, $_customs) && !empty($this->entity->{$var})) { $elements[] = ['element' => 'tr', 'elements' => [ - ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], - ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]], + ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => $data_ref . substr($variable, 1) . '_label']], + ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => $data_ref . substr($variable, 1)]], ]]; } else { - $elements[] = ['element' => 'tr', 'properties' => ['hidden' => $this->entityVariableCheck($variable)], 'elements' => [ - ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1) . '_label']], - ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => 'entity_details-' . substr($variable, 1)]], + $elements[] = ['element' => 'tr', 'properties' => ['hidden' => $hidden_prop], 'elements' => [ + ['element' => 'th', 'content' => $variable . '_label', 'properties' => ['data-ref' => $data_ref . substr($variable, 1) . '_label']], + ['element' => 'th', 'content' => $variable, 'properties' => ['data-ref' => $data_ref . substr($variable, 1)]], ]]; } } @@ -1169,7 +1180,7 @@ class TemplateService }); })->toArray(); - $vendor_details = $include_labels ? $this->labelledFieldStack($vendor_details) : $vendor_details; + $vendor_details = $include_labels ? $this->labelledFieldStack($vendor_details, 'vendor_details-') : $vendor_details; $this->updateElementProperties('vendor-details', $vendor_details); @@ -1295,7 +1306,7 @@ class TemplateService } } - + return $this; } From b003184c65ec1b457f90d2143e76ac545f35f9ee Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 6 Nov 2023 17:18:54 +1100 Subject: [PATCH 08/14] Minor cleanup --- tests/Feature/Template/TemplateTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php index e771f1416a3f..b000d4de5d5e 100644 --- a/tests/Feature/Template/TemplateTest.php +++ b/tests/Feature/Template/TemplateTest.php @@ -509,8 +509,6 @@ class TemplateTest extends TestCase $data['invoices'] = $invoices; $ts = $replicated_design->service()->build($data); - // nlog("results = "); - // nlog($ts->getHtml()); $this->assertNotNull($ts->getHtml()); } From cc96fe9b38b4d9f7ecbb88bf9c711db8d2312d8e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 9 Nov 2023 08:29:35 +1100 Subject: [PATCH 09/14] Cleanup --- tests/Feature/EInvoice/FacturaeTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Feature/EInvoice/FacturaeTest.php b/tests/Feature/EInvoice/FacturaeTest.php index 0770768da14f..2d4053126341 100644 --- a/tests/Feature/EInvoice/FacturaeTest.php +++ b/tests/Feature/EInvoice/FacturaeTest.php @@ -47,7 +47,7 @@ class FacturaeTest extends TestCase $this->assertNotNull($f->run()); - nlog($f->run()); + // nlog($f->run()); // $this->assertTrue($this->validateInvoiceXML($path)); } From 3f30965d54c324cec8b932f950b00b793a7fca3b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 9 Nov 2023 17:59:30 +1100 Subject: [PATCH 10/14] Transformers for Projects and tasks --- app/Models/Task.php | 19 +++++ app/Services/Template/TemplateService.php | 86 +++++++++++++++++------ 2 files changed, 83 insertions(+), 22 deletions(-) diff --git a/app/Models/Task.php b/app/Models/Task.php index be0c86135e8a..083e8094e401 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -237,4 +237,23 @@ class Task extends BaseModel return $this->company->settings->default_task_rate ?? 0; } + + public function processLogs() + { + return + collect($this->timelog)->map(function ($log){ + + $parent_entity = $this->client ?? $this->company; + + if($log[0]) + $log[0] = Carbon::createFromTimestamp($log[0])->format($parent_entity->date_format()); + + if($log[1] && $log[1] != 0) + $log[1] = Carbon::createFromTimestamp($log[1])->format($parent_entity->date_format()); + else + $log[1] = ctrans('texts.running'); + + return $log; + })->toArray(); + } } diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index ef5ad5791618..ca0aa642b0a2 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -795,44 +795,86 @@ class TemplateService * @param mixed $tasks * @return array */ - public function processTasks($tasks): array + public function processTasks($tasks, bool $nested = false): array { - $it = new TaskTransformer(); - $it->setDefaultIncludes(['client','project','invoice']); - $manager = new Manager(); - $resource = new \League\Fractal\Resource\Collection($tasks, $it, null); - $resources = $manager->createData($resource)->toArray(); - foreach($resources['data'] as $key => $resource) { + return collect($tasks)->map(function ($task) use ($nested){ - $resources['data'][$key]['client'] = $resource['client']['data'] ?? []; - $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? []; - $resources['data'][$key]['project'] = $resource['project']['data'] ?? []; - $resources['data'][$key]['invoice'] = $resource['invoice'] ?? []; + return [ + 'number' => (string) $task->number ?: '', + 'description' => (string) $task->description ?: '', + 'duration' => $task->duration ?: 0, + 'rate' => Number::formatMoney($task->rate ?? 0, $task->client ?? $task->company), + 'created_at' => $this->translateDate($task->created_at, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()), + 'updated_at' => $this->translateDate($task->updated_at, $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()), + 'date' => $task->calculated_start_date ? $this->translateDate($task->calculated_start_date , $task->client ? $task->client->date_format() : $task->company->date_format(), $task->client ? $task->client->locale() : $task->company->locale()) : '', + // 'invoice_id' => $this->encodePrimaryKey($task->invoice_id) ?: '', + 'project' => ($task->project && !$nested) ? $this->transformProject($task->project, true) : [], + 'time_log' => $task->processLogs(), + 'custom_value1' => $task->custom_value1 ?: '', + 'custom_value2' => $task->custom_value2 ?: '', + 'custom_value3' => $task->custom_value3 ?: '', + 'custom_value4' => $task->custom_value4 ?: '', + 'status' => $task->status ? $task->status->name : '', + 'client' => $task->client ? [ + 'name' => $task->client->present()->name(), + 'balance' => $task->client->balance, + 'payment_balance' => $task->client->payment_balance, + 'credit_balance' => $task->client->credit_balance, + ] : [], + ]; - } - - return $resources['data']; + })->toArray(); } /** * @todo refactor * - * @param mixed $projects + * @param array | Collection $projects * @return array */ public function processProjects($projects): array { - $it = new ProjectTransformer(); - $it->setDefaultIncludes(['client','tasks']); - $manager = new Manager(); - $manager->setSerializer(new ArraySerializer()); - $resource = new \League\Fractal\Resource\Collection($projects, $it, Project::class); - $i = $manager->createData($resource)->toArray(); - return $i[Project::class]; + return + collect($projects)->map(function ($project) ){ + + return $this->transformProject($project); + + })->toArray(); + + } + + private function transformProject(Project $project, bool $nested = false): array + { + + return [ + 'name' => $project->name ?: '', + 'number' => $project->number ?: '', + 'created_at' => $this->translateDate($project->created_at, $project->client->date_format(), $project->client->locale()), + 'updated_at' => $this->translateDate($project->updated_at, $project->client->date_format(), $project->client->locale()), + 'task_rate' => Number::formatMoney($project->task_rate ?? 0, $project->client), + 'due_date' => $project->due_date ? $this->translateDate($project->due_date, $project->client->date_format(), $project->client->locale()) : '', + 'private_notes' => (string) $project->private_notes ?: '', + 'public_notes' => (string) $project->public_notes ?: '', + 'budgeted_hours' => (float) $project->budgeted_hours, + 'custom_value1' => (string) $project->custom_value1 ?: '', + 'custom_value2' => (string) $project->custom_value2 ?: '', + 'custom_value3' => (string) $project->custom_value3 ?: '', + 'custom_value4' => (string) $project->custom_value4 ?: '', + 'color' => (string) $project->color ?: '', + 'current_hours' => (int) $project->current_hours ?: 0, + 'tasks' => ($project->tasks && !$nested) ? $this->processTasks($project->tasks, true) : [], + 'client' => $project->client ? [ + 'name' => $project->client->present()->name(), + 'balance' => $project->client->balance, + 'payment_balance' => $project->client->payment_balance, + 'credit_balance' => $project->client->credit_balance, + ] : [], + + ]; } From c8408bfa365d428866d13a78e6839e6065d2b48c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 9 Nov 2023 18:08:54 +1100 Subject: [PATCH 11/14] Transformers for Projects and tasks --- app/Services/Template/TemplateService.php | 2 +- tests/Feature/Template/TemplateTest.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index ca0aa642b0a2..e614703cfe88 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -839,7 +839,7 @@ class TemplateService { return - collect($projects)->map(function ($project) ){ + collect($projects)->map(function ($project){ return $this->transformProject($project); diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php index b000d4de5d5e..a9ac8ff19805 100644 --- a/tests/Feature/Template/TemplateTest.php +++ b/tests/Feature/Template/TemplateTest.php @@ -18,6 +18,7 @@ use App\Models\Credit; use App\Models\Design; use App\Models\Invoice; use App\Models\Payment; +use App\Models\Project; use App\Utils\HtmlEngine; use Tests\MockAccountData; use App\Utils\Traits\MakesDates; @@ -180,6 +181,22 @@ class TemplateTest extends TestCase } + public function testProjectDataParse() + { + $data = []; + + $p = Project::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + ]); + + $ts = new TemplateService(); + $ts->processData($data['projects'][$p]); + + $this->assertNotNull($ts); + } + public function testNegativeDivAttribute() { $dom = new \DOMDocument(); From 4cbf10096b3970afc0950e886f92778b13dddee8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Nov 2023 10:09:27 +1100 Subject: [PATCH 12/14] Updates for task time log parsing --- app/Http/Requests/Task/StoreTaskRequest.php | 12 +- app/Http/Requests/Task/UpdateTaskRequest.php | 26 +++- app/Models/Task.php | 2 +- app/Services/Template/TemplateService.php | 25 ++-- tests/Feature/TaskApiTest.php | 123 +++++++++++++++++++ tests/Feature/Template/TemplateTest.php | 4 +- 6 files changed, 171 insertions(+), 21 deletions(-) diff --git a/app/Http/Requests/Task/StoreTaskRequest.php b/app/Http/Requests/Task/StoreTaskRequest.php index 52d331d3ab92..1a71b17f60ff 100644 --- a/app/Http/Requests/Task/StoreTaskRequest.php +++ b/app/Http/Requests/Task/StoreTaskRequest.php @@ -54,7 +54,13 @@ class StoreTaskRequest extends Request $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0'; } - $rules['timelog'] = ['bail','array',function ($attribute, $values, $fail) { + $rules['time_log'] = ['bail',function ($attribute, $values, $fail) { + + if(!is_array(json_decode($values, true))) { + $fail('The '.$attribute.' must be a valid array.'); + return; + } + foreach ($values as $k) { if (!is_int($k[0]) || !is_int($k[1])) { $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); @@ -110,6 +116,10 @@ class StoreTaskRequest extends Request } } + if(!isset($input['time_log']) || empty($input['time_log']) || $input['time_log'] == '{}'){ + $input['time_log'] = json_encode([]); + } + $this->replace($input); } } diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index 7107df9fe63f..141fefed78b1 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -35,26 +35,38 @@ class UpdateTaskRequest extends Request return false; } - return auth()->user()->can('edit', $this->task); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('edit', $this->task); } public function rules() { + /** @var \App\Models\User $user */ + $user = auth()->user(); + $rules = []; if (isset($this->number)) { - $rules['number'] = Rule::unique('tasks')->where('company_id', auth()->user()->company()->id)->ignore($this->task->id); + $rules['number'] = Rule::unique('tasks')->where('company_id', $user->company()->id)->ignore($this->task->id); } if (isset($this->client_id)) { - $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0'; } if (isset($this->project_id)) { - $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.auth()->user()->company()->id.',is_deleted,0'; + $rules['project_id'] = 'bail|required|exists:projects,id,company_id,'.$user->company()->id.',is_deleted,0'; } - $rules['timelog'] = ['bail','array',function ($attribute, $values, $fail) { + $rules['time_log'] = ['bail', function ($attribute, $values, $fail) { + + if(!is_array(json_decode($values, true))) { + $fail('The '.$attribute.' must be a valid array.'); + return; + } + foreach ($values as $k) { if (!is_int($k[0]) || !is_int($k[1])) { $fail('The '.$attribute.' - '.print_r($k, 1).' is invalid. Unix timestamps only.'); @@ -113,6 +125,10 @@ class UpdateTaskRequest extends Request } + if(!isset($input['time_log']) || empty($input['time_log']) || $input['time_log'] == '{}') { + $input['time_log'] = json_encode([]); + } + $this->replace($input); } diff --git a/app/Models/Task.php b/app/Models/Task.php index 083e8094e401..ec0cf54a8afb 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -241,7 +241,7 @@ class Task extends BaseModel public function processLogs() { return - collect($this->timelog)->map(function ($log){ + collect($this->time_log)->map(function ($log){ $parent_entity = $this->client ?? $this->company; diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index e614703cfe88..7b541447e518 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -136,7 +136,7 @@ class TemplateService /** * Initialized a set of HTMLEngine variables * - * @param array | Collection $data + * @param array | \Illuminate\Support\Collection $data * @return self */ private function processVariables($data): self @@ -209,7 +209,7 @@ class TemplateService /** * Process data variables * - * @param array | Collection $data + * @param array | \Illuminate\Support\Collection $data * @return self */ public function processData($data): self @@ -392,7 +392,7 @@ class TemplateService * Pre Processes the Data Blocks into * Twig consumables * - * @param array | Collection $data + * @param array | \Illuminate\Support\Collection $data * @return array */ private function preProcessDataBlocks($data): array @@ -421,7 +421,7 @@ class TemplateService /** * Process Invoices into consumable form for Twig templates * - * @param array | Collection $invoices + * @param array | \Illuminate\Support\Collection $invoices * @return array */ public function processInvoices($invoices): array @@ -506,7 +506,7 @@ class TemplateService * Pads Line Items with raw and formatted content * * @param array $items - * @param mixed $client + * @param Client $client * @return array */ public function padLineItems(array $items, Client $client): array @@ -698,7 +698,7 @@ class TemplateService * Pushes credits through the appropriate transformer * and builds any required relationships * - * @param array | Collection $credits + * @param array | \Illuminate\Support\Collection $credits * @return array */ public function processCredits($credits): array @@ -775,7 +775,7 @@ class TemplateService /** * Pushes payments through the appropriate transformer * - * @param array | Collection $payments + * @param array | \Illuminate\Support\Collection $payments * @return array */ public function processPayments($payments): array @@ -832,7 +832,7 @@ class TemplateService /** * @todo refactor * - * @param array | Collection $projects + * @param array | \Illuminate\Support\Collection $projects * @return array */ public function processProjects($projects): array @@ -900,7 +900,7 @@ class TemplateService /** * Set Company * - * @param mixed $company + * @param Company $company * @return self */ public function setCompany(Company $company): self @@ -955,7 +955,6 @@ class TemplateService }) ->map(function ($stack){ $node = $this->document->getElementById($stack); - nlog(['stack' => $stack, 'labels' => $node->getAttribute('labels')]); return ['stack' => $stack, 'labels' => $node->getAttribute('labels')]; }) ->each(function ($stack) { @@ -976,7 +975,7 @@ class TemplateService { match($stack['stack']) { - 'entity-details' => $this->entityDetails($stack['labels'] == 'true'), + 'entity-details' => $this->entityDetails(), 'client-details' => $this->clientDetails($stack['labels'] == 'true'), 'vendor-details' => $this->vendorDetails($stack['labels'] == 'true'), 'company-details' => $this->companyDetails($stack['labels'] == 'true'), @@ -1010,11 +1009,11 @@ class TemplateService }); })->toArray(); - nlog($company_details); + // nlog($company_details); $company_details = $include_labels ? $this->labelledFieldStack($company_details, 'company_details-') : $company_details; - nlog($company_details); + // nlog($company_details); $this->updateElementProperties('company-details', $company_details); diff --git a/tests/Feature/TaskApiTest.php b/tests/Feature/TaskApiTest.php index 2d7d9e4363fe..065632ea9c82 100644 --- a/tests/Feature/TaskApiTest.php +++ b/tests/Feature/TaskApiTest.php @@ -104,6 +104,90 @@ class TaskApiTest extends TestCase } } + public function testEmptyTimeLogArray() + { + + $data = [ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'description' => 'Test Task', + 'time_log' => null, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/tasks", $data); + + $response->assertStatus(200); + + $data = [ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'description' => 'Test Task', + 'time_log' => '', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/tasks", $data); + + $response->assertStatus(200); + + $data = [ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'description' => 'Test Task', + 'time_log' => '[]', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/tasks", $data); + + $response->assertStatus(200); + + $data = [ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'description' => 'Test Task', + 'time_log' => '{}', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/tasks", $data); + + $response->assertStatus(200); + } + + public function testFaultyTimeLogArray() + { + + $data = [ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'description' => 'Test Task', + 'time_log' => 'ABBA is the best band in the world', + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson("/api/v1/tasks", $data); + + $response->assertStatus(422); + + } + public function testTaskClientRateSet() { $settings = ClientSettings::defaults(); @@ -262,6 +346,45 @@ class TaskApiTest extends TestCase $response->assertStatus(200); + $task->time_log = 'A very strange place'; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray()); + + $response->assertStatus(422); + + $task->time_log = null; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray()); + + $response->assertStatus(200); + + $task->time_log = ''; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray()); + + $response->assertStatus(200); + + + $task->time_log = '{}'; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->putJson("/api/v1/tasks/{$task->hashed_id}?stop=true", $task->toArray()); + + $response->assertStatus(200); + + + } public function testStoppingTaskWithDescription() diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php index a9ac8ff19805..0b6b34621b02 100644 --- a/tests/Feature/Template/TemplateTest.php +++ b/tests/Feature/Template/TemplateTest.php @@ -191,8 +191,10 @@ class TemplateTest extends TestCase 'client_id' => $this->client->id, ]); + $data['projects'][] = $p; + $ts = new TemplateService(); - $ts->processData($data['projects'][$p]); + $ts->processData($data); $this->assertNotNull($ts); } From 670fc5c725430c5a161ba809bb0a1f79f2904275 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Nov 2023 10:42:11 +1100 Subject: [PATCH 13/14] Additional data transformers for templates --- app/Models/PurchaseOrder.php | 3 +- app/Services/Template/TemplateService.php | 179 +++++++++++++++++----- tests/Feature/Template/TemplateTest.php | 61 ++++++++ 3 files changed, 201 insertions(+), 42 deletions(-) diff --git a/app/Models/PurchaseOrder.php b/app/Models/PurchaseOrder.php index cf4841147e6e..72e25660d6d2 100644 --- a/app/Models/PurchaseOrder.php +++ b/app/Models/PurchaseOrder.php @@ -271,7 +271,8 @@ class PurchaseOrder extends BaseModel { return $this->belongsTo(Client::class)->withTrashed(); } - public function markInvitationsSent() + + public function markInvitationsSent(): void { $this->invitations->each(function ($invitation) { if (! isset($invitation->sent_date)) { diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 7b541447e518..1fc4fad5019a 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -23,7 +23,6 @@ use App\Models\Invoice; use App\Models\Payment; use App\Models\Project; use App\Utils\HtmlEngine; -use League\Fractal\Manager; use Twig\Error\LoaderError; use Twig\Error\SyntaxError; use Twig\Error\RuntimeError; @@ -36,12 +35,7 @@ use App\Utils\Traits\MakesDates; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\Traits\Pdf\PdfMaker; use Twig\Extra\Intl\IntlExtension; -use App\Transformers\TaskTransformer; -use App\Transformers\QuoteTransformer; -use App\Transformers\ProjectTransformer; use League\CommonMark\CommonMarkConverter; -use App\Transformers\PurchaseOrderTransformer; -use League\Fractal\Serializer\ArraySerializer; class TemplateService { @@ -365,7 +359,7 @@ class TemplateService $processed = []; - if(in_array($key, ['tasks','projects','aging']) || !$value->first()) { + if(in_array($key, ['tasks','projects', 'aging']) || !$value->first()) { return $processed; } @@ -441,6 +435,8 @@ class TemplateService return [ 'amount' => Number::formatMoney($invoice->amount, $invoice->client), 'balance' => Number::formatMoney($invoice->balance, $invoice->client), + 'status_id' => $invoice->status_id, + 'status' => Invoice::stringStatus($invoice->status_id), 'balance_raw' => $invoice->balance, 'number' => $invoice->number ?: '', 'discount' => $invoice->discount, @@ -506,12 +502,12 @@ class TemplateService * Pads Line Items with raw and formatted content * * @param array $items - * @param Client $client + * @param Vendor | Client $client_or_vendor * @return array */ - public function padLineItems(array $items, Client $client): array + public function padLineItems(array $items, Vendor | Client $client_or_vendor): array { - return collect($items)->map(function ($item) use ($client) { + return collect($items)->map(function ($item) use ($client_or_vendor) { $item->cost_raw = $item->cost ?? 0; $item->discount_raw = $item->discount ?? 0; @@ -520,16 +516,16 @@ class TemplateService $item->tax_amount_raw = $item->tax_amount ?? 0; $item->product_cost_raw = $item->product_cost ?? 0; - $item->cost = Number::formatMoney($item->cost_raw, $client); + $item->cost = Number::formatMoney($item->cost_raw, $client_or_vendor); if($item->is_amount_discount) { - $item->discount = Number::formatMoney($item->discount_raw, $client); + $item->discount = Number::formatMoney($item->discount_raw, $client_or_vendor); } - $item->line_total = Number::formatMoney($item->line_total_raw, $client); - $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client); - $item->tax_amount = Number::formatMoney($item->tax_amount_raw, $client); - $item->product_cost = Number::formatMoney($item->product_cost_raw, $client); + $item->line_total = Number::formatMoney($item->line_total_raw, $client_or_vendor); + $item->gross_line_total = Number::formatMoney($item->gross_line_total_raw, $client_or_vendor); + $item->tax_amount = Number::formatMoney($item->tax_amount_raw, $client_or_vendor); + $item->product_cost = Number::formatMoney($item->product_cost_raw, $client_or_vendor); return $item; @@ -669,28 +665,74 @@ class TemplateService } /** - * @todo refactor + * * - * @param mixed $quotes + * @param array | \Illuminate\Support\Collection $quotes * @return array */ public function processQuotes($quotes): array { - $it = new QuoteTransformer(); - $it->setDefaultIncludes(['client']); - $manager = new Manager(); - $manager->parseIncludes(['client']); - $resource = new \League\Fractal\Resource\Collection($quotes, $it, null); - $resources = $manager->createData($resource)->toArray(); + + return collect($quotes)->map(function ($quote){ - foreach($resources['data'] as $key => $resource) { + return [ + 'amount' => Number::formatMoney($quote->amount, $quote->client), + 'balance' => Number::formatMoney($quote->balance, $quote->client), + 'balance_raw' => (float) $quote->balance, + 'client' => [ + 'name' => $quote->client->present()->name(), + 'balance' => $quote->client->balance, + 'payment_balance' => $quote->client->payment_balance, + 'credit_balance' => $quote->client->credit_balance, + ], + 'status_id' =>$quote->status_id, + 'status' => Quote::stringStatus($quote->status_id), + 'number' => $quote->number ?: '', + 'discount' => (float) $quote->discount, + 'po_number' => $quote->po_number ?: '', + 'date' => $quote->date ? $this->translateDate($quote->date, $quote->client->date_format(), $quote->client->locale()) : '', + 'last_sent_date' => $quote->last_sent_date ? $this->translateDate($quote->last_sent_date, $quote->client->date_format(), $quote->client->locale()) : '', + // 'next_send_date' => $quote->next_send_date ?: '', + // 'reminder1_sent' => $quote->reminder1_sent ?: '', + // 'reminder2_sent' => $quote->reminder2_sent ?: '', + // 'reminder3_sent' => $quote->reminder3_sent ?: '', + // 'reminder_last_sent' => $quote->reminder_last_sent ?: '', + 'due_date' => $quote->due_date ? $this->translateDate($quote->due_date, $quote->client->date_format(), $quote->client->locale()) : '', + 'terms' => $quote->terms ?: '', + 'public_notes' => $quote->public_notes ?: '', + 'private_notes' => $quote->private_notes ?: '', + 'is_deleted' => (bool) $quote->is_deleted, + 'uses_inclusive_taxes' => (bool) $quote->uses_inclusive_taxes, + 'tax_name1' => $quote->tax_name1 ? $quote->tax_name1 : '', + 'tax_rate1' => (float) $quote->tax_rate1, + 'tax_name2' => $quote->tax_name2 ? $quote->tax_name2 : '', + 'tax_rate2' => (float) $quote->tax_rate2, + 'tax_name3' => $quote->tax_name3 ? $quote->tax_name3 : '', + 'tax_rate3' => (float) $quote->tax_rate3, + 'total_taxes' => (float) $quote->total_taxes, + 'is_amount_discount' => (bool) ($quote->is_amount_discount ?: false), + 'footer' => $quote->footer ?: '', + 'partial' => (float) ($quote->partial ?: 0.0), + 'partial_due_date' => $quote->partial_due_date ? $this->translateDate($quote->partial_due_date, $quote->client->date_format(), $quote->client->locale()) : '', + 'custom_value1' => (string) $quote->custom_value1 ?: '', + 'custom_value2' => (string) $quote->custom_value2 ?: '', + 'custom_value3' => (string) $quote->custom_value3 ?: '', + 'custom_value4' => (string) $quote->custom_value4 ?: '', + 'has_expenses' => (bool) $quote->has_expenses, + 'custom_surcharge1' => (float) $quote->custom_surcharge1, + 'custom_surcharge2' => (float) $quote->custom_surcharge2, + 'custom_surcharge3' => (float) $quote->custom_surcharge3, + 'custom_surcharge4' => (float) $quote->custom_surcharge4, + 'custom_surcharge_tax1' => (bool) $quote->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool) $quote->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool) $quote->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool) $quote->custom_surcharge_tax4, + 'line_items' => $quote->line_items ? $this->padLineItems($quote->line_items, $quote->client) : (array) [], + 'exchange_rate' => (float) $quote->exchange_rate, + 'paid_to_date' => (float) $quote->paid_to_date, + ]; - $resources['data'][$key]['client'] = $resource['client']['data'] ?? []; - $resources['data'][$key]['client']['contacts'] = $resource['client']['data']['contacts']['data'] ?? []; - - } - - return $resources['data']; + })->toArray(); } @@ -879,21 +921,76 @@ class TemplateService } /** - * @todo refactor - * - * @param mixed $purchase_orders + * + * @param array | \Illuminate\Support\Collection $purchase_orders * @return array */ public function processPurchaseOrders($purchase_orders): array { - $it = new PurchaseOrderTransformer(); - $it->setDefaultIncludes(['vendor','expense']); - $manager = new Manager(); - $manager->setSerializer(new ArraySerializer()); - $resource = new \League\Fractal\Resource\Collection($purchase_orders, $it, PurchaseOrder::class); - $i = $manager->createData($resource)->toArray(); - return $i[PurchaseOrder::class]; + return collect($purchase_orders)->map(function ($purchase_order){ + + return [ + 'vendor' => $purchase_order->vendor ? [ + 'name' => $purchase_order->vendor->present()->name(), + ] : [], + 'amount' => (float)$purchase_order->amount, + 'balance' => (float)$purchase_order->balance, + 'client' => $purchase_order->client ? [ + 'name' => $purchase_order->client->present()->name(), + 'balance' => $purchase_order->client->balance, + 'payment_balance' => $purchase_order->client->payment_balance, + 'credit_balance' => $purchase_order->client->credit_balance, + ] : [], + 'status_id' => (string)($purchase_order->status_id ?: 1), + 'status' => PurchaseOrder::stringStatus($purchase_order->status_id ?? 1), + 'is_deleted' => (bool)$purchase_order->is_deleted, + 'number' => $purchase_order->number ?: '', + 'discount' => (float)$purchase_order->discount, + 'po_number' => $purchase_order->po_number ?: '', + 'date' => $purchase_order->date ? $this->translateDate($purchase_order->date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'last_sent_date' => $purchase_order->last_sent_date ? $this->translateDate($purchase_order->last_sent_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'next_send_date' => $purchase_order->next_send_date ? $this->translateDate($purchase_order->next_send_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'reminder1_sent' => $purchase_order->reminder1_sent ? $this->translateDate($purchase_order->reminder1_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'reminder2_sent' => $purchase_order->reminder2_sent ? $this->translateDate($purchase_order->reminder2_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'reminder3_sent' => $purchase_order->reminder3_sent ? $this->translateDate($purchase_order->reminder3_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'reminder_last_sent' => $purchase_order->reminder_last_sent ? $this->translateDate($purchase_order->reminder_last_sent, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'due_date' => $purchase_order->due_date ? $this->translateDate($purchase_order->due_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()) : '', + 'terms' => $purchase_order->terms ?: '', + 'public_notes' => $purchase_order->public_notes ?: '', + 'private_notes' => $purchase_order->private_notes ?: '', + 'uses_inclusive_taxes' => (bool)$purchase_order->uses_inclusive_taxes, + 'tax_name1' => $purchase_order->tax_name1 ? $purchase_order->tax_name1 : '', + 'tax_rate1' => (float)$purchase_order->tax_rate1, + 'tax_name2' => $purchase_order->tax_name2 ? $purchase_order->tax_name2 : '', + 'tax_rate2' => (float)$purchase_order->tax_rate2, + 'tax_name3' => $purchase_order->tax_name3 ? $purchase_order->tax_name3 : '', + 'tax_rate3' => (float)$purchase_order->tax_rate3, + 'total_taxes' => (float)$purchase_order->total_taxes, + 'is_amount_discount' => (bool)($purchase_order->is_amount_discount ?: false), + 'footer' => $purchase_order->footer ?: '', + 'partial' => (float)($purchase_order->partial ?: 0.0), + 'partial_due_date' => $purchase_order->partial_due_date ? $this->translateDate($purchase_order->partial_due_date, $purchase_order->vendor->date_format(), $purchase_order->vendor->locale()): '', + 'custom_value1' => (string)$purchase_order->custom_value1 ?: '', + 'custom_value2' => (string)$purchase_order->custom_value2 ?: '', + 'custom_value3' => (string)$purchase_order->custom_value3 ?: '', + 'custom_value4' => (string)$purchase_order->custom_value4 ?: '', + 'has_tasks' => (bool)$purchase_order->has_tasks, + 'has_expenses' => (bool)$purchase_order->has_expenses, + 'custom_surcharge1' => (float)$purchase_order->custom_surcharge1, + 'custom_surcharge2' => (float)$purchase_order->custom_surcharge2, + 'custom_surcharge3' => (float)$purchase_order->custom_surcharge3, + 'custom_surcharge4' => (float)$purchase_order->custom_surcharge4, + 'custom_surcharge_tax1' => (bool)$purchase_order->custom_surcharge_tax1, + 'custom_surcharge_tax2' => (bool)$purchase_order->custom_surcharge_tax2, + 'custom_surcharge_tax3' => (bool)$purchase_order->custom_surcharge_tax3, + 'custom_surcharge_tax4' => (bool)$purchase_order->custom_surcharge_tax4, + 'line_items' => $purchase_order->line_items ? $this->padLineItems($purchase_order->line_items, $purchase_order->vendor): (array)[], + 'exchange_rate' => (float)$purchase_order->exchange_rate, + 'currency_id' => $purchase_order->currency_id ? (string) $purchase_order->currency_id : '', + ]; + + })->toArray(); } diff --git a/tests/Feature/Template/TemplateTest.php b/tests/Feature/Template/TemplateTest.php index 0b6b34621b02..088efb3495f7 100644 --- a/tests/Feature/Template/TemplateTest.php +++ b/tests/Feature/Template/TemplateTest.php @@ -181,6 +181,65 @@ class TemplateTest extends TestCase } + + public function testPurchaseOrderDataParse() + { + $data = []; + + $p = \App\Models\PurchaseOrder::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'vendor_id' => $this->vendor->id, + ]); + + $data['purchase_orders'][] = $p; + + $ts = new TemplateService(); + $ts->processData($data); + + $this->assertNotNull($ts); + $this->assertIsArray($ts->getData()); + } + + public function testTaskDataParse() + { + $data = []; + + $p = \App\Models\Task::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + ]); + + $data['tasks'][] = $p; + + $ts = new TemplateService(); + $ts->processData($data); + + $this->assertNotNull($ts); + $this->assertIsArray($ts->getData()); + } + + public function testQuoteDataParse() + { + $data = []; + + $p = \App\Models\Quote::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + ]); + + $data['quotes'][] = $p; + + $ts = new TemplateService(); + $ts->processData($data); + + $this->assertNotNull($ts); + $this->assertIsArray($ts->getData()); + + } + public function testProjectDataParse() { $data = []; @@ -197,6 +256,8 @@ class TemplateTest extends TestCase $ts->processData($data); $this->assertNotNull($ts); + $this->assertIsArray($ts->getData()); + } public function testNegativeDivAttribute() From 8d8aa4a4e3e511fb9271ac187dd239f650042c00 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 10 Nov 2023 10:58:09 +1100 Subject: [PATCH 14/14] Minor fixes --- app/Services/Template/TemplateService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 1fc4fad5019a..7ced0b7a588e 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -359,7 +359,7 @@ class TemplateService $processed = []; - if(in_array($key, ['tasks','projects', 'aging']) || !$value->first()) { + if(in_array($key, ['tasks', 'projects', 'aging']) || !$value->first()) { return $processed; }