From 1d28a98a55b990438bc0eeac68dc448d9094b453 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Dec 2022 13:22:01 +1100 Subject: [PATCH 01/36] Stubs for refactor for PDF generation --- app/Jobs/Entity/CreateEntityPdf.php | 1 + app/Models/Client.php | 15 --- app/Models/Vendor.php | 5 + app/Services/Pdf/PdfConfiguration.php | 158 ++++++++++++++++++++++++++ app/Services/Pdf/PdfService.php | 42 +++++++ tests/Pdf/PdfServiceTest.php | 68 +++++++++++ 6 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 app/Services/Pdf/PdfConfiguration.php create mode 100644 app/Services/Pdf/PdfService.php create mode 100644 tests/Pdf/PdfServiceTest.php diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php index fe4b0ccccd4c..0f388ade0b3c 100644 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ b/app/Jobs/Entity/CreateEntityPdf.php @@ -111,6 +111,7 @@ class CreateEntityPdf implements ShouldQueue /* Init a new copy of the translator*/ $t = app('translator'); + /* Set the locale*/ App::setLocale($this->client->locale()); diff --git a/app/Models/Client.php b/app/Models/Client.php index 7b09fa68f80e..5f33ed84c4bc 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -324,21 +324,6 @@ class Client extends BaseModel implements HasLocalePreference return $this->service()->updateBalance($amount); } - /** - * Adjusts client "balances" when a client - * makes a payment that goes on file, but does - * not effect the client.balance record. - * - * @param float $amount Adjustment amount - * @return Client - */ - // public function processUnappliedPayment($amount) :Client - // { - // return $this->service()->updatePaidToDate($amount) - // ->adjustCreditBalance($amount) - // ->save(); - // } - /** * Returns the entire filtered set * of settings which have been merged from diff --git a/app/Models/Vendor.php b/app/Models/Vendor.php index abde54bf77c2..48812f028cac 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -171,6 +171,11 @@ class Vendor extends BaseModel return ''; } + public function getMergedSettings() :object + { + return $this->company->settings; + } + public function purchase_order_filepath($invitation) { $contact_key = $invitation->contact->contact_key; diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php new file mode 100644 index 000000000000..0cb663f5c11b --- /dev/null +++ b/app/Services/Pdf/PdfConfiguration.php @@ -0,0 +1,158 @@ +service = $service; + + } + + public function init() + { + + $this->setEntityType() + ->setEntityProperties() + ->setDesign(); + + return $this; + + } + + private function setEntityType() + { + + if ($this->service->invitation instanceof InvoiceInvitation) { + $this->entity = $this->service->invitation->invoice; + $this->entity_string = 'invoice'; + } elseif ($this->service->invitation instanceof QuoteInvitation) { + $this->entity = $this->service->invitation->quote; + $this->entity_string = 'quote'; + } elseif ($this->service->invitation instanceof CreditInvitation) { + $this->entity = $this->service->invitation->credit; + $this->entity_string = 'credit'; + } elseif ($this->service->invitation instanceof RecurringInvoiceInvitation) { + $this->entity = $this->service->invitation->recurring_invoice; + $this->entity_string = 'recurring_invoice'; + } elseif ($this->service->invitation instanceof PurchaseOrderInvitation) { + $this->entity = $this->service->invitation->purchase_order; + $this->entity_string = 'purchase_order'; + } else { + throw new \Exception('Unable to resolve entity', 500); + } + + return $this; + } + + private function setEntityProperties() + { + $entity_design_id = ''; + + if ($this->entity instanceof Invoice) { + + $this->client = $this->entity->client; + $this->path = $this->client->invoice_filepath($this->service->invitation); + $this->entity_design_id = 'invoice_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + + } elseif ($this->entity instanceof Quote) { + + $this->client = $this->entity->client; + $this->path = $this->client->quote_filepath($this->service->invitation); + $this->entity_design_id = 'quote_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + + } elseif ($this->entity instanceof Credit) { + + $this->client = $this->entity->client; + $this->path = $this->client->credit_filepath($this->service->invitation); + $this->entity_design_id = 'credit_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + + } elseif ($this->entity instanceof RecurringInvoice) { + + $this->client = $this->entity->client; + $this->path = $this->client->recurring_invoice_filepath($this->service->invitation); + $this->entity_design_id = 'invoice_design_id'; + $this->settings = $this->client->getMergedSettings(); + $this->settings_object = $this->client; + + } elseif ($this->entity instanceof PurchaseOrder) { + + $this->vendor = $this->entity->vendor; + $this->path = $this->vendor->purchase_order_filepath($this->service->invitation); + $this->entity_design_id = 'invoice_design_id'; + $this->entity_design_id = 'purchase_order_design_id'; + $this->settings = $this->vendor->getMergedSettings(); + $this->settings_object = $this->client; + + } + else + throw new \Exception('Unable to resolve entity properties type', 500); + + $this->path = $this->path.$this->entity->numberFormatter().'.pdf'; + + return $this; + } + + private function setDesign() + { + + $design_id = $this->entity->design_id ? : $this->decodePrimaryKey($this->settings_object->getSetting($this->entity_design_id)); + + $this->design = Design::find($design_id ?: 2); + + return $this; + + } + +} \ No newline at end of file diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php new file mode 100644 index 000000000000..306bc92f0f9d --- /dev/null +++ b/app/Services/Pdf/PdfService.php @@ -0,0 +1,42 @@ +invitation = $invitation; + + $this->config = (new PdfConfiguration($this))->init(); + + } + + public function getPdf() + { + + } + + public function getHtml() + { + + } + +} \ No newline at end of file diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php new file mode 100644 index 000000000000..30371619b10e --- /dev/null +++ b/tests/Pdf/PdfServiceTest.php @@ -0,0 +1,68 @@ +makeTestData(); + } + + public function testInitOfClass() + { + + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertInstanceOf(PdfService::class, $service); + + } + + public function testEntityResolution() + { + + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertInstanceOf(PdfConfiguration::class, $service->config); + + + } + + public function testDefaultDesign() + { + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertEquals(2, $service->config->design->id); + + } +} \ No newline at end of file From bce476977bec840f9bcfb39d85955b5d4264adb6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Dec 2022 13:46:52 +1100 Subject: [PATCH 02/36] Resolve design template --- app/Services/Pdf/PdfService.php | 44 +++++++++++++++++++++++++++++++++ tests/Pdf/PdfServiceTest.php | 20 +++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 306bc92f0f9d..043adcf85a9a 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -12,6 +12,7 @@ namespace App\Services\Pdf; use App\Services\Pdf\PdfConfiguration; +use App\Utils\HtmlEngine; class PdfService { @@ -20,6 +21,12 @@ class PdfService public PdfConfiguration $config; + public PdfBuilder $builder; + + public PdfDesigner $designer; + + public array $html_variables; + public function __construct($invitation) { @@ -27,6 +34,17 @@ class PdfService $this->config = (new PdfConfiguration($this))->init(); + $this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); + + $this->builder = (new PdfBuilder($this)); + + $this->designer = (new PdfDesigner($this))->build(); + } + + public function build() + { + $this->builder->build(); + } public function getPdf() @@ -39,4 +57,30 @@ class PdfService } + + // $state = [ + // 'template' => $template->elements([ + // 'client' => $this->client, + // 'entity' => $this->entity, + // 'pdf_variables' => (array) $this->company->settings->pdf_variables, + // '$product' => $design->design->product, + // 'variables' => $variables, + // ]), + // 'variables' => $variables, + // 'options' => [ + // 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), + // 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), + // ], + // 'process_markdown' => $this->entity->client->company->markdown_enabled, + // ]; + + // $maker = new PdfMakerService($state); + + // $maker + // ->design($template) + // ->build(); + + + + } \ No newline at end of file diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index 30371619b10e..b4d39ba936b4 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -65,4 +65,24 @@ class PdfServiceTest extends TestCase $this->assertEquals(2, $service->config->design->id); } + + public function testHtmlIsArray() + { + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertIsArray($service->html_variables); + + } + + public function testTemplateResolution() + { + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertIsString($service->designer->template); + + } } \ No newline at end of file From 5cf629d5dea2546cdaa758053d22d4ad6895b542 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Dec 2022 13:58:08 +1100 Subject: [PATCH 03/36] Resolve design template --- app/Services/Pdf/PdfBuilder.php | 139 +++++++++++++++++++++++++++++++ app/Services/Pdf/PdfDesigner.php | 80 ++++++++++++++++++ app/Services/PdfMaker/Design.php | 4 - 3 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 app/Services/Pdf/PdfBuilder.php create mode 100644 app/Services/Pdf/PdfDesigner.php diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php new file mode 100644 index 000000000000..cbe7f402f544 --- /dev/null +++ b/app/Services/Pdf/PdfBuilder.php @@ -0,0 +1,139 @@ +service = $service; + } + + public function build() + { + + $this->getTemplate() + ->buildSections(); + + } + + private function getTemplate() :self + { + + $document = new DOMDocument(); + + $document->validateOnParse = true; + @$document->loadHTML(mb_convert_encoding($this->service->config->designer->template, 'HTML-ENTITIES', 'UTF-8')); + + $this->document = $document; + $this->xpath = new DOMXPath($document); + + return $this; + } + + private function buildSections() :self + { + $this->sections = [ + 'company-details' => [ + 'id' => 'company-details', + 'elements' => $this->companyDetails(), + ], + 'company-address' => [ + 'id' => 'company-address', + 'elements' => $this->companyAddress(), + ], + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDetails(), + ], + 'vendor-details' => [ + 'id' => 'vendor-details', + 'elements' => $this->vendorDetails(), + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->entityDetails(), + ], + 'delivery-note-table' => [ + 'id' => 'delivery-note-table', + 'elements' => $this->deliveryNoteTable(), + ], + 'product-table' => [ + 'id' => 'product-table', + 'elements' => $this->productTable(), + ], + 'task-table' => [ + 'id' => 'task-table', + 'elements' => $this->taskTable(), + ], + 'statement-invoice-table' => [ + 'id' => 'statement-invoice-table', + 'elements' => $this->statementInvoiceTable(), + ], + 'statement-invoice-table-totals' => [ + 'id' => 'statement-invoice-table-totals', + 'elements' => $this->statementInvoiceTableTotals(), + ], + 'statement-payment-table' => [ + 'id' => 'statement-payment-table', + 'elements' => $this->statementPaymentTable(), + ], + 'statement-payment-table-totals' => [ + 'id' => 'statement-payment-table-totals', + 'elements' => $this->statementPaymentTableTotals(), + ], + 'statement-aging-table' => [ + 'id' => 'statement-aging-table', + 'elements' => $this->statementAgingTable(), + ], + 'table-totals' => [ + 'id' => 'table-totals', + 'elements' => $this->tableTotals(), + ], + 'footer-elements' => [ + 'id' => 'footer', + 'elements' => [ + $this->sharedFooterElements(), + ], + ], + ]; + + return $this; + + } + + + + // if (isset($this->data['template']) && isset($this->data['variables'])) { + // $this->getEmptyElements($this->data['template'], $this->data['variables']); + // } + + // if (isset($this->data['template'])) { + // $this->updateElementProperties($this->data['template']); + // } + + // if (isset($this->data['variables'])) { + // $this->updateVariables($this->data['variables']); + // } + + // return $this; + + + + +} \ No newline at end of file diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php new file mode 100644 index 000000000000..0f7cf9671226 --- /dev/null +++ b/app/Services/Pdf/PdfDesigner.php @@ -0,0 +1,80 @@ +service = $service; + } + + public function build() :self + { + + /*If the design is custom*/ + if ($this->service->config->design->is_custom) + { + + $this->template = $this->composeFromPartials(json_decode(json_encode($this->service->config->design->design), true)); + + } + else + { + $this->template = file_get_contents(config('ninja.designs.base_path') . strtolower($this->service->config->design->name) . '.html'); + } + + return $this; + } + + /** + * If the user has implemented a custom design, then we need to rebuild the design at this point + */ + private function composeFromPartials(array $partials) :string + { + $html = ''; + + $html .= $partials['includes']; + $html .= $partials['header']; + $html .= $partials['body']; + $html .= $partials['footer']; + + return $html; + } + + + + + +} \ No newline at end of file diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index df0d16ebae21..3ca76e076f0e 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -172,10 +172,6 @@ class Design extends BaseDesign $this->sharedFooterElements(), ], ], - // 'swiss-qr' => [ - // 'id' => 'swiss-qr', - // 'elements' => $this->swissQrCodeElement(), - // ] ]; } From d9c84b275bc53849e6afc7c9415111a3c3f8c5fa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 23 Dec 2022 20:51:24 +1100 Subject: [PATCH 04/36] Resolve design template --- app/Services/Pdf/PdfConfiguration.php | 24 ++++++++++++++++++++++++ app/Services/Pdf/PdfService.php | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 0cb663f5c11b..64b17df36b1c 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -11,6 +11,7 @@ namespace App\Services\Pdf; +use App\DataMapper\CompanySettings; use App\Models\Client; use App\Models\Credit; use App\Models\CreditInvitation; @@ -47,6 +48,8 @@ class PdfConfiguration public $service; + public array $pdf_variables; + public function __construct(PdfService $service) { @@ -59,12 +62,33 @@ class PdfConfiguration $this->setEntityType() ->setEntityProperties() + ->setPdfVariables() ->setDesign(); return $this; } + private function setPdfVariables() :self + { + + $default = (array) CompanySettings::getEntityVariableDefaults(); + $variables = $this->service->company->pdf_variables; + + foreach ($default as $property => $value) { + if (array_key_exists($property, $variables)) { + continue; + } + + $variables[$property] = $value; + } + + $this->pdf_variables = $variables; + + return $this; + + } + private function setEntityType() { diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 043adcf85a9a..7264100242fe 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -19,6 +19,8 @@ class PdfService public $invitation; + public Company $company; + public PdfConfiguration $config; public PdfBuilder $builder; @@ -32,6 +34,8 @@ class PdfService $this->invitation = $invitation; + $this->company = $invitation->company; + $this->config = (new PdfConfiguration($this))->init(); $this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); From cb3ecf005b3cdaa17de41656f7f1744c973eedbf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Dec 2022 02:44:12 +1100 Subject: [PATCH 05/36] Refactors for design --- app/Services/Pdf/PdfBuilder.php | 638 ++++++++++++++++++++++++-- app/Services/Pdf/PdfConfiguration.php | 11 + app/Services/Pdf/PdfService.php | 18 +- 3 files changed, 618 insertions(+), 49 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index cbe7f402f544..0fc357c9263c 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -11,14 +11,19 @@ namespace App\Services\Pdf; +use App\Utils\Number; +use App\Utils\Traits\MakesDates; use DOMDocument; use DOMXPath; class PdfBuilder { + use MakesDates; public PdfService $service; + public array $sections = []; + public function __construct(PdfService $service) { $this->service = $service; @@ -46,9 +51,85 @@ class PdfBuilder return $this; } - private function buildSections() :self + private function getProductSections(): self { - $this->sections = [ + $this->genericSectionBuilder() + ->getClientDetails() + ->getProductAndTaskTables() + ->getProductEntityDetails() + ->getProductTotals(); + } + + private function getDeliveryNoteSections(): self + { + + $this->sections[] = [ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDeliveryDetails(), + ], + 'delivery-note-table' => [ + 'id' => 'delivery-note-table', + 'elements' => $this->deliveryNoteTable(), + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->deliveryNoteDetails(), + ], + ]; + + return $this; + + } + + private function getStatementSections(): self + { + + // 'statement-invoice-table' => [ + // 'id' => 'statement-invoice-table', + // 'elements' => $this->statementInvoiceTable(), + // ], + // 'statement-invoice-table-totals' => [ + // 'id' => 'statement-invoice-table-totals', + // 'elements' => $this->statementInvoiceTableTotals(), + // ], + // 'statement-payment-table' => [ + // 'id' => 'statement-payment-table', + // 'elements' => $this->statementPaymentTable(), + // ], + // 'statement-payment-table-totals' => [ + // 'id' => 'statement-payment-table-totals', + // 'elements' => $this->statementPaymentTableTotals(), + // ], + // 'statement-aging-table' => [ + // 'id' => 'statement-aging-table', + // 'elements' => $this->statementAgingTable(), + // ], + + } + + private function getPurchaseOrderSections(): self + { + + $this->sections[] = [ + 'vendor-details' => [ + 'id' => 'vendor-details', + 'elements' => $this->vendorDetails(), + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->purchaseOrderDetails(), + ], + ]; + + return $this; + + } + + private function genericSectionBuilder(): self + { + + $this->sections[] = [ 'company-details' => [ 'id' => 'company-details', 'elements' => $this->companyDetails(), @@ -57,22 +138,226 @@ class PdfBuilder 'id' => 'company-address', 'elements' => $this->companyAddress(), ], - 'client-details' => [ - 'id' => 'client-details', - 'elements' => $this->clientDetails(), + 'footer-elements' => [ + 'id' => 'footer', + 'elements' => [ + $this->sharedFooterElements(), + ], ], - 'vendor-details' => [ - 'id' => 'vendor-details', - 'elements' => $this->vendorDetails(), - ], - 'entity-details' => [ - 'id' => 'entity-details', - 'elements' => $this->entityDetails(), - ], - 'delivery-note-table' => [ - 'id' => 'delivery-note-table', - 'elements' => $this->deliveryNoteTable(), + ]; + + return $this; + } + + private function getProductTotals(): self + { + + $this->sections[] = [ + 'table-totals' => [ + 'id' => 'table-totals', + 'elements' => $this->tableTotals(), ], + ]; + + return $this; + } + + private function getProductEntityDetails(): self + { + + + if($this->service->config->entity_string == 'invoice') + { + $this->sections[] = [ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->invoiceDetails(), + ], + ]; + } + elseif($this->service->config->entity_string == 'quote') + { + + $this->sections[] = [ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->quoteDetails(), + ], + ]; + + } + elseif($this->service->config->entity_string == 'credit') + { + + $this->sections[] = [ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->creditDetails(), + ], + ]; + + } + + return $this; + + + } + + private function buildSections() :self + { + + return match ($this->service->config->document_type) { + PdfService::PRODUCT => $this->getProductSections, + PdfService::DELIVERY_NOTE => $this->getDeliveryNoteSections(), + PdfService::STATEMENT => $this->getStatementSections(), + PdfService::PURCHASE_ORDER => $this->getPurchaseOrderSections(), + }; + + } + + private function statementTableTotals(): array + { + return [ + ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ + ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: block; align-items: flex-start; page-break-inside: avoid; visible !important;'], 'elements' => [ + ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->service->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']], + ]], + ]], + ]; + } + + //todo, split this down into each entity to make this more readable + public function getTableTotals() :self + { + //need to see where we don't pass all these particular variables. try and refactor thisout + $_variables = array_key_exists('variables', $this->context) + ? $this->context['variables'] + : ['values' => ['$entity.public_notes' => $this->service->config->entity->public_notes, '$entity.terms' => $this->service->config->entity->terms, '$entity_footer' => $this->service->config->entity->footer], 'labels' => []]; + + $variables = $this->service->config->pdf_variables['total_columns']; + + $elements = [ + ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ + ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], + ['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [ + ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], + ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], + ]], + ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']], + ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start; page-break-inside: auto;'], 'elements' => [ + ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->service->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']], + ]], + ]], + ['element' => 'div', 'properties' => ['class' => 'totals-table-right-side', 'dir' => '$dir'], 'elements' => []], + ]; + + + if ($this->type == self::DELIVERY_NOTE) { + return $elements; + } + + if ($this->entity instanceof Quote) { + // We don't want to show Balanace due on the quotes. + if (in_array('$outstanding', $variables)) { + $variables = \array_diff($variables, ['$outstanding']); + } + + if ($this->service->config->entity->partial > 0) { + $variables[] = '$partial_due'; + } + } + + if ($this->entity instanceof Credit) { + // We don't want to show Balanace due on the quotes. + if (in_array('$paid_to_date', $variables)) { + $variables = \array_diff($variables, ['$paid_to_date']); + } + + } + + foreach (['discount'] as $property) { + $variable = sprintf('%s%s', '$', $property); + + if ( + !is_null($this->service->config->entity->{$property}) && + !empty($this->service->config->entity->{$property}) && + $this->service->config->entity->{$property} != 0 + ) { + continue; + } + + $variables = array_filter($variables, function ($m) use ($variable) { + return $m != $variable; + }); + } + + foreach ($variables as $variable) { + if ($variable == '$total_taxes') { + $taxes = $this->service->config->entity->calc()->getTotalTaxMap(); + + if (!$taxes) { + continue; + } + + foreach ($taxes as $i => $tax) { + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']], + ['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->entity instanceof \App\Models\PurchaseOrder ? $this->vendor : $this->client), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], + ]]; + } + } elseif ($variable == '$line_taxes') { + $taxes = $this->service->config->entity->calc()->getTaxMap(); + + if (!$taxes) { + continue; + } + + foreach ($taxes as $i => $tax) { + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']], + ['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->entity instanceof \App\Models\PurchaseOrder ? $this->vendor : $this->client), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]], + ]]; + } + } elseif (Str::startsWith($variable, '$custom_surcharge')) { + $_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1 + + $visible = intval($this->service->config->entity->{$_variable}) != 0; + + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], + ]]; + } elseif (Str::startsWith($variable, '$custom')) { + $field = explode('_', $variable); + $visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && !empty($this->company->custom_fields->{$field[1]}); + + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]], + ]]; + } else { + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], + ['element' => 'span', 'content' => $variable, 'properties' => ['data-ref' => 'totals_table-' . substr($variable, 1)]], + ]]; + } + } + + $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ + ['element' => 'span', 'content' => '',], + ['element' => 'span', 'content' => ''], + ]]; + + return $elements; + + + } + + + public function getProductAndTaskTables(): self + { + + $this->sections[] = [ 'product-table' => [ 'id' => 'product-table', 'elements' => $this->productTable(), @@ -81,42 +366,299 @@ class PdfBuilder 'id' => 'task-table', 'elements' => $this->taskTable(), ], - 'statement-invoice-table' => [ - 'id' => 'statement-invoice-table', - 'elements' => $this->statementInvoiceTable(), - ], - 'statement-invoice-table-totals' => [ - 'id' => 'statement-invoice-table-totals', - 'elements' => $this->statementInvoiceTableTotals(), - ], - 'statement-payment-table' => [ - 'id' => 'statement-payment-table', - 'elements' => $this->statementPaymentTable(), - ], - 'statement-payment-table-totals' => [ - 'id' => 'statement-payment-table-totals', - 'elements' => $this->statementPaymentTableTotals(), - ], - 'statement-aging-table' => [ - 'id' => 'statement-aging-table', - 'elements' => $this->statementAgingTable(), - ], - 'table-totals' => [ - 'id' => 'table-totals', - 'elements' => $this->tableTotals(), - ], - 'footer-elements' => [ - 'id' => 'footer', - 'elements' => [ - $this->sharedFooterElements(), - ], - ], - ]; + ]; return $this; - } + public function getClientDetails(): self + { + $this->sections[] = [ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDetails(), + ], + ]; + + return $this; + } + +/** + * Parent method for building products table. + * + * @return array + */ + public function productTable(): array + { + $product_items = collect($this->service->config->entity->line_items)->filter(function ($item) { + return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5; + }); + + if (count($product_items) == 0) { + return []; + } + + // if ($this->type === self::DELIVERY_NOTE || $this->type === self::STATEMENT) { + // return []; + // } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('product')], + ['element' => 'tbody', 'elements' => $this->buildTableBody('$product')], + ]; + } + + /** + * Parent method for building tasks table. + * + * @return array + */ + public function taskTable(): array + { + $task_items = collect($this->service->config->entity->line_items)->filter(function ($item) { + return $item->type_id == 2; + }); + + if (count($task_items) == 0) { + return []; + } + + // if ($this->type === self::DELIVERY_NOTE || $this->type === self::STATEMENT) { + // return []; + // } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('task')], + ['element' => 'tbody', 'elements' => $this->buildTableBody('$task')], + ]; + } + + + + + public function statementDetails(): array + { + + $s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale()); + + return [ + ['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => ""], + ['element' => 'th', 'properties' => [], 'content' => "

".ctrans('texts.statement')."

"], + ]], + ['element' => 'tr', 'properties' => [], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => ctrans('texts.statement_date')], + ['element' => 'th', 'properties' => [], 'content' => $s_date ?? ''], + ]], + ['element' => 'tr', 'properties' => [], 'elements' => [ + ['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'], + ['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)], + ]], + ]; + + } + + public function invoiceDetails(): array + { + + $variables = $this->service->config->pdf_variables['invoice_details']; + + return $this->genericDetailsBuilder($variables); + } + + public function quoteDetails(): array + { + $variables = $this->service->config->pdf_variables['quote_details']; + + if ($this->service->config->entity->partial > 0) { + $variables[] = '$quote.balance_due'; + } + + return $this->genericDetailsBuilder($variables); + } + + public function creditDetails(): array + { + + $variables = $this->service->config->pdf_variables['credit_details']; + + return $this->genericDetailsBuilder($variables); + } + + public function purchaseOrderDetails(): array + { + + $variables = $this->service->config->pdf_variables['purchase_order_details']; + + return $this->genericDetailsBuilder($variables); + + } + + public function deliveryNoteDetails(): array + { + + $variables = $this->service->config->pdf_variables['invoice_details']; + + $variables = array_filter($variables, function ($m) { + return !in_array($m, ['$invoice.balance_due', '$invoice.total']); + }); + + return $this->genericDetailsBuilder($variables); + } + + public function genericDetailsBuilder(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->service->config->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; + } + + + + public function clientDeliveryDetails(): array + { + + $elements = []; + + if(!$this->service->config->client) + return $elements; + + $elements = [ + ['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], + ['element' => 'p', 'content' => $this->service->config->client->name, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.name']], + ['element' => 'p', 'content' => $this->service->config->client->shipping_address1, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address1']], + ['element' => 'p', 'content' => $this->service->config->client->shipping_address2, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-client.shipping_address2']], + ['element' => 'p', 'show_empty' => false, 'elements' => [ + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_city} ", 'properties' => ['ref' => 'delivery_note-client.shipping_city']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_state} ", 'properties' => ['ref' => 'delivery_note-client.shipping_state']], + ['element' => 'span', 'content' => "{$this->service->config->client->shipping_postal_code} ", 'properties' => ['ref' => 'delivery_note-client.shipping_postal_code']], + ]], + ['element' => 'p', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], + ]; + + if (!is_null($this->service->config->contact)) { + $elements[] = ['element' => 'p', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; + } + + return $elements; + + } + + public function clientDetails(): array + { + $elements = []; + + if(!$this->service->config->client) + return $elements; + + $variables = $this->service->config->pdf_variables['client_details']; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'client_details-' . substr($variable, 1)]]; + } + + return $elements; + } + + //todo + public function deliveryNoteTable(): array + { + + $thead = [ + ['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']], + ['element' => 'th', 'content' => '$description_label', 'properties' => ['data-ref' => 'delivery_note-description_label']], + ['element' => 'th', 'content' => '$product.quantity_label', 'properties' => ['data-ref' => 'delivery_note-product.quantity_label']], + ]; + + $items = $this->transformLineItems($this->service->config->entity->line_items, $this->type); + + $this->processNewLines($items); + $product_customs = [false, false, false, false]; + + foreach ($items as $row) { + for ($i = 0; $i < count($product_customs); $i++) { + if (!empty($row['delivery_note.delivery_note' . ($i + 1)])) { + $product_customs[$i] = true; + } + } + } + + for ($i = 0; $i < count($product_customs); $i++) { + if ($product_customs[$i]) { + array_push($thead, ['element' => 'th', 'content' => '$product.product' . ($i + 1) . '_label', 'properties' => ['data-ref' => 'delivery_note-product.product' . ($i + 1) . '_label']]); + } + } + + return [ + ['element' => 'thead', 'elements' => $thead], + ['element' => 'tbody', 'elements' => $this->buildTableBody(self::DELIVERY_NOTE)], + ]; + } + + + public function companyDetails(): array + { + $variables = $this->service->config->pdf_variables['company_details']; + + $elements = []; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + } + + return $elements; + } + + public function companyAddress(): array + { + $variables = $this->service->config->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; + } + + public function vendorDetails(): array + { + $elements = []; + + $variables = $this->service->config->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; + } + + + + // if (isset($this->data['template']) && isset($this->data['variables'])) { diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 64b17df36b1c..7f7973be9262 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -13,6 +13,7 @@ namespace App\Services\Pdf; use App\DataMapper\CompanySettings; use App\Models\Client; +use App\Models\ClientContact; use App\Models\Credit; use App\Models\CreditInvitation; use App\Models\Design; @@ -25,6 +26,7 @@ use App\Models\QuoteInvitation; use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; use App\Models\Vendor; +use App\Models\VendorContact; use App\Services\Pdf\PdfService; use App\Utils\Traits\MakesHash; @@ -36,8 +38,12 @@ class PdfConfiguration public ?Client $client; + public ?ClientContact $contact; + public ?Vendor $vendor; + public ?VendorContact $vendor_contact; + public object $settings; public $settings_object; @@ -121,6 +127,7 @@ class PdfConfiguration if ($this->entity instanceof Invoice) { $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; $this->path = $this->client->invoice_filepath($this->service->invitation); $this->entity_design_id = 'invoice_design_id'; $this->settings = $this->client->getMergedSettings(); @@ -129,6 +136,7 @@ class PdfConfiguration } elseif ($this->entity instanceof Quote) { $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; $this->path = $this->client->quote_filepath($this->service->invitation); $this->entity_design_id = 'quote_design_id'; $this->settings = $this->client->getMergedSettings(); @@ -137,6 +145,7 @@ class PdfConfiguration } elseif ($this->entity instanceof Credit) { $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; $this->path = $this->client->credit_filepath($this->service->invitation); $this->entity_design_id = 'credit_design_id'; $this->settings = $this->client->getMergedSettings(); @@ -145,6 +154,7 @@ class PdfConfiguration } elseif ($this->entity instanceof RecurringInvoice) { $this->client = $this->entity->client; + $this->contact = $this->service->invitation->contact; $this->path = $this->client->recurring_invoice_filepath($this->service->invitation); $this->entity_design_id = 'invoice_design_id'; $this->settings = $this->client->getMergedSettings(); @@ -153,6 +163,7 @@ class PdfConfiguration } elseif ($this->entity instanceof PurchaseOrder) { $this->vendor = $this->entity->vendor; + $this->vendor_contact = $this->service->invitation->contact; $this->path = $this->vendor->purchase_order_filepath($this->service->invitation); $this->entity_design_id = 'invoice_design_id'; $this->entity_design_id = 'purchase_order_design_id'; diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 7264100242fe..43b0d8aa55a8 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -11,8 +11,10 @@ namespace App\Services\Pdf; +use App\Models\Account; use App\Services\Pdf\PdfConfiguration; use App\Utils\HtmlEngine; +use App\Models\Company; class PdfService { @@ -21,6 +23,8 @@ class PdfService public Company $company; + public Account $account; + public PdfConfiguration $config; public PdfBuilder $builder; @@ -29,13 +33,22 @@ class PdfService public array $html_variables; - public function __construct($invitation) + public string $document_type; + + const DELIVERY_NOTE = 'delivery_note'; + const STATEMENT = 'statement'; + const PURCHASE_ORDER = 'purchase_order'; + const PRODUCT = 'product'; + + public function __construct($invitation, $document_type = 'product') { $this->invitation = $invitation; $this->company = $invitation->company; + $this->account = $this->company->account; + $this->config = (new PdfConfiguration($this))->init(); $this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); @@ -43,6 +56,9 @@ class PdfService $this->builder = (new PdfBuilder($this)); $this->designer = (new PdfDesigner($this))->build(); + + $this->document_type = $document_type; + } public function build() From eec9fa4dbc8efb659634b2050f0809a0fde535e2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Dec 2022 19:31:43 +1100 Subject: [PATCH 06/36] Refactor for designer --- app/Services/Pdf/PdfBuilder.php | 740 ++++++++++++++++++++++++-- app/Services/Pdf/PdfConfiguration.php | 23 +- app/Services/Pdf/PdfService.php | 8 +- app/Utils/Helpers.php | 2 +- 4 files changed, 714 insertions(+), 59 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 0fc357c9263c..1f852ff9781b 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -11,10 +11,15 @@ namespace App\Services\Pdf; +use App\Models\Credit; +use App\Models\Quote; +use App\Utils\Helpers; use App\Utils\Number; use App\Utils\Traits\MakesDates; use DOMDocument; use DOMXPath; +use Illuminate\Support\Carbon; +use Illuminate\Support\Str; class PdfBuilder { @@ -53,16 +58,23 @@ class PdfBuilder private function getProductSections(): self { + $this->genericSectionBuilder() ->getClientDetails() ->getProductAndTaskTables() ->getProductEntityDetails() ->getProductTotals(); + + return $this; + } private function getDeliveryNoteSections(): self { + $this->genericSectionBuilder() + ->getProductTotals(); + $this->sections[] = [ 'client-details' => [ 'id' => 'client-details', @@ -85,32 +97,139 @@ class PdfBuilder private function getStatementSections(): self { - // 'statement-invoice-table' => [ - // 'id' => 'statement-invoice-table', - // 'elements' => $this->statementInvoiceTable(), - // ], - // 'statement-invoice-table-totals' => [ - // 'id' => 'statement-invoice-table-totals', - // 'elements' => $this->statementInvoiceTableTotals(), - // ], - // 'statement-payment-table' => [ - // 'id' => 'statement-payment-table', - // 'elements' => $this->statementPaymentTable(), - // ], - // 'statement-payment-table-totals' => [ - // 'id' => 'statement-payment-table-totals', - // 'elements' => $this->statementPaymentTableTotals(), - // ], - // 'statement-aging-table' => [ - // 'id' => 'statement-aging-table', - // 'elements' => $this->statementAgingTable(), - // ], + $this->genericSectionBuilder(); + + $this->sections[] = [ + 'statement-invoice-table' => [ + 'id' => 'statement-invoice-table', + 'elements' => $this->statementInvoiceTable(), + ], + 'statement-invoice-table-totals' => [ + 'id' => 'statement-invoice-table-totals', + 'elements' => $this->statementInvoiceTableTotals(), + ], + 'statement-payment-table' => [ + 'id' => 'statement-payment-table', + 'elements' => $this->statementPaymentTable(), + ], + 'statement-payment-table-totals' => [ + 'id' => 'statement-payment-table-totals', + 'elements' => $this->statementPaymentTableTotals(), + ], + 'statement-aging-table' => [ + 'id' => 'statement-aging-table', + 'elements' => $this->statementAgingTable(), + ], + 'table-totals' => [ + 'id' => 'table-totals', + 'elements' => $this->statementTableTotals(), + ], + ]; + + return $this; } + + public function statementInvoiceTableTotals(): array + { + + $outstanding = $this->service->options['invoices']->sum('balance'); + + return [ + ['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->service->config->client)], + ]; + } + + + /** + * Parent method for building payments table within statement. + * + * @return array + */ + public function statementPaymentTable(): array + { + if (is_null($this->service->option['payments'])) { + return []; + } + + if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + return []; + } + + $tbody = []; + + //24-03-2022 show payments per invoice + foreach ($this->service->options['invoices'] as $invoice) { + foreach ($invoice->payments as $payment) { + + if($payment->is_deleted) + continue; + + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($payment->date, $this->service->config->client->date_format(), $this->service->config->client->locale()) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')]; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($payment->pivot->amount, $this->service->config->client) ?: ' ']; + + $tbody[] = $element; + + } + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + } + + public function statementPaymentTableTotals(): array + { + if (is_null($this->service->options['payments']) || !$this->service->options['payments']->first()) { + return []; + } + + if (\array_key_exists('show_payments_table', $this->service->options) && $this->service->options['show_payments_table'] === false) { + return []; + } + + $payment = $this->service->options['payments']->first(); + + return [ + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->service->options['payments']->sum('amount'), $this->service->config->client))], + ]; + } + + public function statementAgingTable(): array + { + + if (\array_key_exists('show_aging_table', $this->service->options) && $this->service->options['show_aging_table'] === false) { + return []; + } + + $elements = [ + ['element' => 'thead', 'elements' => []], + ['element' => 'tbody', 'elements' => [ + ['element' => 'tr', 'elements' => []], + ]], + ]; + + foreach ($this->service->options['aging'] as $column => $value) { + $elements[0]['elements'][] = ['element' => 'th', 'content' => $column]; + $elements[1]['elements'][] = ['element' => 'td', 'content' => $value]; + } + + return $elements; + } + + private function getPurchaseOrderSections(): self { + $this->genericSectionBuilder() + ->getProductTotals(); + $this->sections[] = [ 'vendor-details' => [ 'id' => 'vendor-details', @@ -132,11 +251,11 @@ class PdfBuilder $this->sections[] = [ 'company-details' => [ 'id' => 'company-details', - 'elements' => $this->companyDetails(), + 'elements' => $this->service->companyDetails(), ], 'company-address' => [ 'id' => 'company-address', - 'elements' => $this->companyAddress(), + 'elements' => $this->service->companyAddress(), ], 'footer-elements' => [ 'id' => 'footer', @@ -149,13 +268,392 @@ class PdfBuilder return $this; } + public function statementInvoiceTable(): array + { + + $tbody = []; + + foreach ($this->service->options['invoices'] as $invoice) { + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->client->date_format(), $this->client->locale()) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $this->client) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $this->client) ?: ' ']; + + $tbody[] = $element; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_invoice')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + } + + + /** + * Generate the structure of table body. () + * + * @param string $type "$product" or "$task" + * @return array + */ + public function buildTableBody(string $type): array + { + $elements = []; + + $items = $this->transformLineItems($this->entity->line_items, $type); + + $this->processNewLines($items); + + if (count($items) == 0) { + return []; + } + + if ($type == PdfService::DELIVERY_NOTE) { + $product_customs = [false, false, false, false]; + + foreach ($items as $row) { + for ($i = 0; $i < count($product_customs); $i++) { + if (!empty($row['delivery_note.delivery_note' . ($i + 1)])) { + $product_customs[$i] = true; + } + } + } + + foreach ($items as $row) { + $element = ['element' => 'tr', 'elements' => []]; + + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.product_key'], 'properties' => ['data-ref' => 'delivery_note_table.product_key-td']]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.notes'], 'properties' => ['data-ref' => 'delivery_note_table.notes-td']]; + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.quantity'], 'properties' => ['data-ref' => 'delivery_note_table.quantity-td']]; + + for ($i = 0; $i < count($product_customs); $i++) { + if ($product_customs[$i]) { + $element['elements'][] = ['element' => 'td', 'content' => $row['delivery_note.delivery_note' . ($i + 1)], 'properties' => ['data-ref' => 'delivery_note_table.product' . ($i + 1) . '-td']]; + } + } + + $elements[] = $element; + } + + return $elements; + } + + foreach ($items as $row) { + $element = ['element' => 'tr', 'elements' => []]; + + if ( + array_key_exists($type, $this->context) && + !empty($this->context[$type]) && + !is_null($this->context[$type]) + ) { + $document = new DOMDocument(); + $document->loadHTML($this->context[$type], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + + $td = $document->getElementsByTagName('tr')->item(0); + + if ($td) { + foreach ($td->childNodes as $child) { + if ($child->nodeType !== 1) { + continue; + } + + if ($child->tagName !== 'td') { + continue; + } + + $element['elements'][] = ['element' => 'td', 'content' => strtr($child->nodeValue, $row)]; + } + } + } else { + $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; + + foreach ($this->context['pdf_variables']["{$_type}_columns"] as $key => $cell) { + // We want to keep aliases like these: + // $task.cost => $task.rate + // $task.quantity => $task.hours + + if ($cell == '$task.rate') { + $element['elements'][] = ['element' => 'td', 'content' => $row['$task.cost'], 'properties' => ['data-ref' => 'task_table-task.cost-td']]; + } elseif ($cell == '$product.discount' && !$this->service->company->enable_product_discount) { + $element['elements'][] = ['element' => 'td', 'content' => $row['$product.discount'], 'properties' => ['data-ref' => 'product_table-product.discount-td', 'style' => 'display: none;']]; + } elseif ($cell == '$product.quantity' && !$this->service->company->enable_product_quantity) { + $element['elements'][] = ['element' => 'td', 'content' => $row['$product.quantity'], 'properties' => ['data-ref' => 'product_table-product.quantity-td', 'style' => 'display: none;']]; + } elseif ($cell == '$task.hours') { + $element['elements'][] = ['element' => 'td', 'content' => $row['$task.quantity'], 'properties' => ['data-ref' => 'task_table-task.hours-td']]; + } elseif ($cell == '$product.tax_rate1') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax1-td']]; + } elseif ($cell == '$product.tax_rate2') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax2-td']]; + } elseif ($cell == '$product.tax_rate3') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax3-td']]; + } else if ($cell == '$product.unit_cost' || $cell == '$task.rate') { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; + } else { + $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; + } + } + } + + $elements[] = $element; + } + + $document = null; + + return $elements; + } + + + + /** + * Formats the line items for display. + * + * @param mixed $items + * @param string $table_type + * @param mixed|null $custom_fields + * + * @return array + */ + public function transformLineItems($items, $table_type = '$product') :array + { + + $data = []; + + if (! is_array($items)) { + } + + $locale_info = localeconv(); + + $this->service->config->entity_currency = $this->service->config->currency; + + foreach ($items as $key => $item) { + if ($table_type == '$product' && $item->type_id != 1) { + if ($item->type_id != 4 && $item->type_id != 6 && $item->type_id != 5) { + continue; + } + } + + if ($table_type == '$task' && $item->type_id != 2) { + // if ($item->type_id != 4 && $item->type_id != 5) { + continue; + // } + } + + $helpers = new Helpers(); + $_table_type = ltrim($table_type, '$'); // From $product -> product. + + $data[$key][$table_type.'.product_key'] = is_null(optional($item)->product_key) ? $item->item : $item->product_key; + $data[$key][$table_type.'.item'] = is_null(optional($item)->item) ? $item->product_key : $item->item; + $data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service; + + $currentDateTime = null; + if (isset($this->entity->next_send_date)) { + $currentDateTime = Carbon::parse($this->entity->next_send_date); + } + + $data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); + $data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); + + $data[$key][$table_type.".{$_table_type}1"] = strlen($item->custom_value1) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}1", $item->custom_value1, $this->service->config->currency_entity) : ''; + $data[$key][$table_type.".{$_table_type}2"] = strlen($item->custom_value2) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}2", $item->custom_value2, $this->service->config->currency_entity) : ''; + $data[$key][$table_type.".{$_table_type}3"] = strlen($item->custom_value3) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}3", $item->custom_value3, $this->service->config->currency_entity) : ''; + $data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) >= 1 ? $helpers->formatCustomFieldValue($this->service->company->custom_fields, "{$_table_type}4", $item->custom_value4, $this->service->config->currency_entity) : ''; + + if ($item->quantity > 0 || $item->cost > 0) { + $data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $this->service->config->currency_entity); + + $data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $this->service->config->currency_entity); + + $data[$key][$table_type.'.cost'] = Number::formatMoney($item->cost, $this->service->config->currency_entity); + + $data[$key][$table_type.'.line_total'] = Number::formatMoney($item->line_total, $this->service->config->currency_entity); + } else { + $data[$key][$table_type.'.quantity'] = ''; + + $data[$key][$table_type.'.unit_cost'] = ''; + + $data[$key][$table_type.'.cost'] = ''; + + $data[$key][$table_type.'.line_total'] = ''; + } + + if (property_exists($item, 'gross_line_total')) { + $data[$key][$table_type.'.gross_line_total'] = ($item->gross_line_total == 0) ? '' : Number::formatMoney($item->gross_line_total, $this->service->config->currency_entity); + } else { + $data[$key][$table_type.'.gross_line_total'] = ''; + } + + if (property_exists($item, 'tax_amount')) { + $data[$key][$table_type.'.tax_amount'] = ($item->tax_amount == 0) ? '' : Number::formatMoney($item->tax_amount, $this->service->config->currency_entity); + } else { + $data[$key][$table_type.'.tax_amount'] = ''; + } + + if (isset($item->discount) && $item->discount > 0) { + if ($item->is_amount_discount) { + $data[$key][$table_type.'.discount'] = Number::formatMoney($item->discount, $this->service->config->currency_entity); + } else { + $data[$key][$table_type.'.discount'] = floatval($item->discount).'%'; + } + } else { + $data[$key][$table_type.'.discount'] = ''; + } + + // Previously we used to check for tax_rate value, + // but that's no longer necessary. + + if (isset($item->tax_rate1)) { + $data[$key][$table_type.'.tax_rate1'] = floatval($item->tax_rate1).'%'; + $data[$key][$table_type.'.tax1'] = &$data[$key][$table_type.'.tax_rate1']; + } + + if (isset($item->tax_rate2)) { + $data[$key][$table_type.'.tax_rate2'] = floatval($item->tax_rate2).'%'; + $data[$key][$table_type.'.tax2'] = &$data[$key][$table_type.'.tax_rate2']; + } + + if (isset($item->tax_rate3)) { + $data[$key][$table_type.'.tax_rate3'] = floatval($item->tax_rate3).'%'; + $data[$key][$table_type.'.tax3'] = &$data[$key][$table_type.'.tax_rate3']; + } + + $data[$key]['task_id'] = property_exists($item, 'task_id') ? $item->task_id : ''; + } + + //nlog(microtime(true) - $start); + + return $data; + } + + /** + * Generate the structure of table headers. () + * + * @param string $type "product" or "task" + * @return array + */ + public function buildTableHeader(string $type): array + { + $this->processTaxColumns($type); + // $this->processCustomColumns($type); + + $elements = []; + + // Some of column can be aliased. This is simple workaround for these. + $aliases = [ + '$product.product_key' => '$product.item', + '$task.product_key' => '$task.service', + '$task.rate' => '$task.cost', + ]; + + foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) { + if (array_key_exists($column, $aliases)) { + $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->service->config->settings_object->getSetting('hide_empty_columns_on_pdf')]]; + } elseif ($column == '$product.discount' && !$this->service->company->enable_product_discount) { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; + } elseif ($column == '$product.quantity' && !$this->service->company->enable_product_quantity) { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'style' => 'display: none;']]; + } elseif ($column == '$product.tax_rate1') { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax1-th", 'hidden' => $this->service->config->settings_object->getSetting('hide_empty_columns_on_pdf')]]; + } elseif ($column == '$product.tax_rate2') { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax2-th", 'hidden' => $this->service->config->settings_object->getSetting('hide_empty_columns_on_pdf')]]; + } elseif ($column == '$product.tax_rate3') { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-product.tax3-th", 'hidden' => $this->service->config->settings_object->getSetting('hide_empty_columns_on_pdf')]]; + } else { + $elements[] = ['element' => 'th', 'content' => $column . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($column, 1) . '-th', 'hidden' => $this->service->config->settings_object->getSetting('hide_empty_columns_on_pdf')]]; + } + } + + return $elements; + } + + /** + * This method will help us decide either we show + * one "tax rate" column in the table or 3 custom tax rates. + * + * Logic below will help us calculate that & inject the result in the + * global state of the $context (design state). + * + * @param string $type "product" or "task" + * @return void + */ + public function processTaxColumns(string $type): void + { + if ($type == 'product') { + $type_id = 1; + } + + if ($type == 'task') { + $type_id = 2; + } + + // At the moment we pass "task" or "product" as type. + // However, "pdf_variables" contains "$task.tax" or "$product.tax" <-- Notice the dollar sign. + // This sprintf() will help us convert "task" or "product" into "$task" or "$product" without + // evaluating the variable. + + if (in_array(sprintf('%s%s.tax', '$', $type), (array) $this->service->config->pdf_variables["{$type}_columns"])) { + $line_items = collect($this->service->config->entity->line_items)->filter(function ($item) use ($type_id) { + return $item->type_id = $type_id; + }); + + $tax1 = $line_items->where('tax_name1', '<>', '')->where('type_id', $type_id)->count(); + $tax2 = $line_items->where('tax_name2', '<>', '')->where('type_id', $type_id)->count(); + $tax3 = $line_items->where('tax_name3', '<>', '')->where('type_id', $type_id)->count(); + + $taxes = []; + + if ($tax1 > 0) { + array_push($taxes, sprintf('%s%s.tax_rate1', '$', $type)); + } + + if ($tax2 > 0) { + array_push($taxes, sprintf('%s%s.tax_rate2', '$', $type)); + } + + if ($tax3 > 0) { + array_push($taxes, sprintf('%s%s.tax_rate3', '$', $type)); + } + + $key = array_search(sprintf('%s%s.tax', '$', $type), $this->service->config->pdf_variables["{$type}_columns"], true); + + if ($key !== false) { + array_splice($this->service->config->pdf_variables["{$type}_columns"], $key, 1, $taxes); + } + } + } + + + public function sharedFooterElements(): array + { + // We want to show headers for statements, no exceptions. + $statements = " + document.querySelectorAll('#statement-invoice-table > thead > tr > th, #statement-payment-table > thead > tr > th, #statement-aging-table > thead > tr > th').forEach(t => { + t.hidden = false; + }); + "; + + $javascript = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{if(""!==t.innerText){let e=t.getAttribute("data-ref").slice(0,-3);document.querySelector(`th[data-ref="${e}-th"]`).removeAttribute("hidden")}}),document.querySelectorAll("#product-table > tbody > tr > td, #task-table > tbody > tr > td, #delivery-note-table > tbody > tr > td").forEach(t=>{let e=t.getAttribute("data-ref").slice(0,-3);(e=document.querySelector(`th[data-ref="${e}-th"]`)).hasAttribute("hidden")&&""==t.innerText&&t.setAttribute("hidden","true")})},!1);'; + + // Previously we've been decoding the HTML on the backend and XML parsing isn't good options because it requires, + // strict & valid HTML to even output/decode. Decoding is now done on the frontend with this piece of Javascript. + + $html_decode = 'document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll(`[data-state="encoded-html"]`).forEach(e=>e.innerHTML=e.innerText)},!1);'; + + return ['element' => 'div', 'elements' => [ + ['element' => 'script', 'content' => $statements], + ['element' => 'script', 'content' => $javascript], + ['element' => 'script', 'content' => $html_decode], + ]]; + } + private function getProductTotals(): self { $this->sections[] = [ 'table-totals' => [ 'id' => 'table-totals', - 'elements' => $this->tableTotals(), + 'elements' => $this->getTableTotals(), ], ]; @@ -203,6 +701,7 @@ class PdfBuilder } + /* Parent entry point when building sections of the design content */ private function buildSections() :self { @@ -226,22 +725,55 @@ class PdfBuilder ]; } - //todo, split this down into each entity to make this more readable - public function getTableTotals() :self + 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})) { + return true; + } + + if (empty($this->entity->{$_variable})) { + return true; + } + + return false; + } + + //First pass done, need a second pass to abstract this content completely. + public function getTableTotals() :array { //need to see where we don't pass all these particular variables. try and refactor thisout $_variables = array_key_exists('variables', $this->context) ? $this->context['variables'] - : ['values' => ['$entity.public_notes' => $this->service->config->entity->public_notes, '$entity.terms' => $this->service->config->entity->terms, '$entity_footer' => $this->service->config->entity->footer], 'labels' => []]; + : ['values' => ['$this->service->config->entity.public_notes' => $this->service->config->entity->public_notes, '$this->service->config->entity.terms' => $this->service->config->entity->terms, '$this->service->config->entity_footer' => $this->service->config->entity->footer], 'labels' => []]; $variables = $this->service->config->pdf_variables['total_columns']; $elements = [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ - ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], + ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$this->service->config->entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], ['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [ - ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], - ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], + ['element' => 'span', 'content' => '$this->service->config->entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$this->service->config->entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], + ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$this->service->config->entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], ]], ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']], ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start; page-break-inside: auto;'], 'elements' => [ @@ -252,11 +784,11 @@ class PdfBuilder ]; - if ($this->type == self::DELIVERY_NOTE) { + if ($this->service->config->document_type == PdfService::DELIVERY_NOTE) { return $elements; } - if ($this->entity instanceof Quote) { + if ($this->service->config->entity instanceof Quote) { // We don't want to show Balanace due on the quotes. if (in_array('$outstanding', $variables)) { $variables = \array_diff($variables, ['$outstanding']); @@ -267,7 +799,7 @@ class PdfBuilder } } - if ($this->entity instanceof Credit) { + if ($this->service->config->entity instanceof Credit) { // We don't want to show Balanace due on the quotes. if (in_array('$paid_to_date', $variables)) { $variables = \array_diff($variables, ['$paid_to_date']); @@ -302,7 +834,7 @@ class PdfBuilder foreach ($taxes as $i => $tax) { $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i . '-label']], - ['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->entity instanceof \App\Models\PurchaseOrder ? $this->vendor : $this->client), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], + ['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->service->config->currency_entity), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], ]]; } } elseif ($variable == '$line_taxes') { @@ -315,7 +847,7 @@ class PdfBuilder foreach ($taxes as $i => $tax) { $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ ['element' => 'span', 'content', 'content' => $tax['name'], 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i . '-label']], - ['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->entity instanceof \App\Models\PurchaseOrder ? $this->vendor : $this->client), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]], + ['element' => 'span', 'content', 'content' => Number::formatMoney($tax['total'], $this->service->config->entity instanceof \App\Models\PurchaseOrder ? $this->service->config->vendor : $this->service->config->client), 'properties' => ['data-ref' => 'totals-table-line_tax_' . $i]], ]]; } } elseif (Str::startsWith($variable, '$custom_surcharge')) { @@ -329,7 +861,7 @@ class PdfBuilder ]]; } elseif (Str::startsWith($variable, '$custom')) { $field = explode('_', $variable); - $visible = is_object($this->company->custom_fields) && property_exists($this->company->custom_fields, $field[1]) && !empty($this->company->custom_fields->{$field[1]}); + $visible = is_object($this->service->company->custom_fields) && property_exists($this->service->company->custom_fields, $field[1]) && !empty($this->service->company->custom_fields->{$field[1]}); $elements[1]['elements'][] = ['element' => 'div', 'elements' => [ ['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']], @@ -350,10 +882,14 @@ class PdfBuilder return $elements; - } - + /** + * Generates the product and task tables + * + * @return self + * + */ public function getProductAndTaskTables(): self { @@ -371,6 +907,12 @@ class PdfBuilder return $this; } + /** + * Generates the client details + * + * @return self + * + */ public function getClientDetails(): self { $this->sections[] = [ @@ -384,7 +926,7 @@ class PdfBuilder } /** - * Parent method for building products table. + * Generates the product table * * @return array */ @@ -398,10 +940,6 @@ class PdfBuilder return []; } - // if ($this->type === self::DELIVERY_NOTE || $this->type === self::STATEMENT) { - // return []; - // } - return [ ['element' => 'thead', 'elements' => $this->buildTableHeader('product')], ['element' => 'tbody', 'elements' => $this->buildTableBody('$product')], @@ -409,7 +947,7 @@ class PdfBuilder } /** - * Parent method for building tasks table. + * Generates the task table * * @return array */ @@ -423,10 +961,6 @@ class PdfBuilder return []; } - // if ($this->type === self::DELIVERY_NOTE || $this->type === self::STATEMENT) { - // return []; - // } - return [ ['element' => 'thead', 'elements' => $this->buildTableHeader('task')], ['element' => 'tbody', 'elements' => $this->buildTableBody('$task')], @@ -434,12 +968,16 @@ class PdfBuilder } - - + /** + * Generates the statement details + * + * @return array + * + */ public function statementDetails(): array { - $s_date = $this->translateDate(now(), $this->client->date_format(), $this->client->locale()); + $s_date = $this->translateDate(now(), $this->service->config->client->date_format(), $this->service->config->client->locale()); return [ ['element' => 'tr', 'properties' => ['data-ref' => 'statement-label'], 'elements' => [ @@ -452,12 +990,18 @@ class PdfBuilder ]], ['element' => 'tr', 'properties' => [], 'elements' => [ ['element' => 'th', 'properties' => [], 'content' => '$balance_due_label'], - ['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->invoices->sum('balance'), $this->client)], + ['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->service->options['invoices']->sum('balance'), $this->service->config->client)], ]], ]; } + /** + * Generates the invoice details + * + * @return array + * + */ public function invoiceDetails(): array { @@ -466,6 +1010,12 @@ class PdfBuilder return $this->genericDetailsBuilder($variables); } + /** + * Generates the quote details + * + * @return array + * + */ public function quoteDetails(): array { $variables = $this->service->config->pdf_variables['quote_details']; @@ -477,6 +1027,13 @@ class PdfBuilder return $this->genericDetailsBuilder($variables); } + + /** + * Generates the credit note details + * + * @return array + * + */ public function creditDetails(): array { @@ -485,6 +1042,11 @@ class PdfBuilder return $this->genericDetailsBuilder($variables); } + /** + * Generates the purchase order details + * + * @return array + */ public function purchaseOrderDetails(): array { @@ -494,6 +1056,12 @@ class PdfBuilder } + /** + * Generates the deliveyr note details + * + * @return array + * + */ public function deliveryNoteDetails(): array { @@ -506,6 +1074,13 @@ class PdfBuilder return $this->genericDetailsBuilder($variables); } + /** + * Generates the custom values for the + * entity. + * + * @param array + * @return array + */ public function genericDetailsBuilder(array $variables): array { @@ -535,7 +1110,13 @@ class PdfBuilder } - + /** + * Generates the client delivery + * details array + * + * @return array + * + */ public function clientDeliveryDetails(): array { @@ -565,6 +1146,11 @@ class PdfBuilder } + /** + * Generates the client details section + * + * @return array + */ public function clientDetails(): array { $elements = []; @@ -581,9 +1167,14 @@ class PdfBuilder return $elements; } - //todo + /** + * Generates the delivery note table + * + * @return array + */ public function deliveryNoteTable(): array { + /* Static array of delivery note columns*/ $thead = [ ['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']], @@ -591,9 +1182,10 @@ class PdfBuilder ['element' => 'th', 'content' => '$product.quantity_label', 'properties' => ['data-ref' => 'delivery_note-product.quantity_label']], ]; - $items = $this->transformLineItems($this->service->config->entity->line_items, $this->type); + $items = $this->transformLineItems($this->service->config->entity->line_items, $this->service->config->document_type); $this->processNewLines($items); + $product_customs = [false, false, false, false]; foreach ($items as $row) { @@ -612,11 +1204,35 @@ class PdfBuilder return [ ['element' => 'thead', 'elements' => $thead], - ['element' => 'tbody', 'elements' => $this->buildTableBody(self::DELIVERY_NOTE)], + ['element' => 'tbody', 'elements' => $this->buildTableBody(PdfService::DELIVERY_NOTE)], ]; } + /** + * Passes an array of items by reference + * and performs a nl2br + * + * @param array + * @return void + * + */ + public function processNewLines(array &$items): void + { + foreach ($items as $key => $item) { + foreach ($item as $variable => $value) { + $item[$variable] = str_replace("\n", '
', $value); + } + $items[$key] = $item; + } + } + + /** + * Generates an arary of the company details + * + * @return array + * + */ public function companyDetails(): array { $variables = $this->service->config->pdf_variables['company_details']; @@ -630,6 +1246,13 @@ class PdfBuilder return $elements; } + /** + * + * Generates an array of the company address + * + * @return array + * + */ public function companyAddress(): array { $variables = $this->service->config->pdf_variables['company_address']; @@ -643,6 +1266,13 @@ class PdfBuilder return $elements; } + /** + * + * Generates an array of vendor details + * + * @return array + * + */ public function vendorDetails(): array { $elements = []; diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 7f7973be9262..08fd10fe2777 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -16,6 +16,7 @@ use App\Models\Client; use App\Models\ClientContact; use App\Models\Credit; use App\Models\CreditInvitation; +use App\Models\Currency; use App\Models\Design; use App\Models\Invoice; use App\Models\InvoiceInvitation; @@ -56,6 +57,14 @@ class PdfConfiguration public array $pdf_variables; + public Currency $currency; + + /** + * The parent object of the currency + * @var App\Models\Client | App\Models\Vendor + */ + public $currency_entity; + public function __construct(PdfService $service) { @@ -69,7 +78,19 @@ class PdfConfiguration $this->setEntityType() ->setEntityProperties() ->setPdfVariables() - ->setDesign(); + ->setDesign() + ->setCurrency(); + + return $this; + + } + + private function setCurrency() :self + { + + $this->currency = $this->client ? $this->client->currency() : $this->vendor->currency(); + + $this->currency_entity = $this->client ? $this->client : $this->vendor; return $this; diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 43b0d8aa55a8..51e7d0cf9400 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -35,12 +35,14 @@ class PdfService public string $document_type; + public array $options; + const DELIVERY_NOTE = 'delivery_note'; const STATEMENT = 'statement'; const PURCHASE_ORDER = 'purchase_order'; const PRODUCT = 'product'; - public function __construct($invitation, $document_type = 'product') + public function __construct($invitation, $document_type = 'product', $options = []) { $this->invitation = $invitation; @@ -48,7 +50,7 @@ class PdfService $this->company = $invitation->company; $this->account = $this->company->account; - + $this->config = (new PdfConfiguration($this))->init(); $this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); @@ -59,6 +61,8 @@ class PdfService $this->document_type = $document_type; + $this->options = $options; + } public function build() diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index e2797049b2b3..bbe69b3e7a46 100644 --- a/app/Utils/Helpers.php +++ b/app/Utils/Helpers.php @@ -105,7 +105,7 @@ class Helpers * Process reserved keywords on PDF. * * @param string $value - * @param Client|Company $entity + * @param Client|Company|Vendor $entity * @param null|Carbon $currentDateTime * @return null|string */ From 2fe91b5707ea06c5b4b0054cd64b2c2d7968b6c5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Dec 2022 20:29:51 +1100 Subject: [PATCH 07/36] Fixes for casting pdf_variables --- app/Services/Pdf/PdfBuilder.php | 349 +++++++++++++++++++++++--- app/Services/Pdf/PdfConfiguration.php | 2 +- 2 files changed, 315 insertions(+), 36 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 1f852ff9781b..30aae5471bcc 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -27,21 +27,58 @@ class PdfBuilder public PdfService $service; + /** + * an array of sections to be injected into the template + * @var array + */ public array $sections = []; + /** + * @param PdfService $service + * @return void + */ public function __construct(PdfService $service) { $this->service = $service; } - public function build() + /** + * Builds the template sections + * + * @return self + * + */ + public function build(): self { $this->getTemplate() - ->buildSections(); + ->buildSections() + ->getEmptyElements() + ->updateElementProperties() + ->updateVariables(); + return $this; } + /** + * Final method to get compiled HTML. + * + * @param bool $final @deprecated // is it? i still see it being called elsewhere + * @return mixed + */ + public function getCompiledHTML($final = false) + { + $html = $this->document->saveHTML(); + + return str_replace('%24', '$', $html); + } + + /** + * Generate the template + * + * @return self + * + */ private function getTemplate() :self { @@ -56,6 +93,12 @@ class PdfBuilder return $this; } + /** + * Generates product entity sections + * + * @return self + * + */ private function getProductSections(): self { @@ -69,6 +112,12 @@ class PdfBuilder } + /** + * Generates delivery note sections + * + * @return self + * + */ private function getDeliveryNoteSections(): self { @@ -94,6 +143,12 @@ class PdfBuilder } + /** + * Generates statement sections + * + * @return self + * + */ private function getStatementSections(): self { @@ -130,7 +185,12 @@ class PdfBuilder } - + /** + * Parent method for building invoice table totals + * for statements. + * + * @return array + */ public function statementInvoiceTableTotals(): array { @@ -184,6 +244,12 @@ class PdfBuilder ]; } + /** + * Generates the statement payments table + * + * @return array + * + */ public function statementPaymentTableTotals(): array { if (is_null($this->service->options['payments']) || !$this->service->options['payments']->first()) { @@ -201,6 +267,12 @@ class PdfBuilder ]; } + /** + * Generates the statement aging table + * + * @return array + * + */ public function statementAgingTable(): array { @@ -224,6 +296,12 @@ class PdfBuilder } + /** + * Generates the purchase order sections + * + * @return self + * + */ private function getPurchaseOrderSections(): self { @@ -245,17 +323,24 @@ class PdfBuilder } + /** + * Generates the generic section which apply + * across all design templates + * + * @return self + * + */ private function genericSectionBuilder(): self { $this->sections[] = [ 'company-details' => [ 'id' => 'company-details', - 'elements' => $this->service->companyDetails(), + 'elements' => $this->companyDetails(), ], 'company-address' => [ 'id' => 'company-address', - 'elements' => $this->service->companyAddress(), + 'elements' => $this->companyAddress(), ], 'footer-elements' => [ 'id' => 'footer', @@ -268,6 +353,12 @@ class PdfBuilder return $this; } + /** + * Generates the invoices table for statements + * + * @return array + * + */ public function statementInvoiceTable(): array { @@ -277,10 +368,10 @@ class PdfBuilder $element = ['element' => 'tr', 'elements' => []]; $element['elements'][] = ['element' => 'td', 'content' => $invoice->number]; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->client->date_format(), $this->client->locale()) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $this->client) ?: ' ']; - $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $this->client) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->date, $this->client->date_format(), $this->service->config->client->locale()) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->client->date_format(), $this->service->config->client->locale()) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->amount, $this->service->config->client) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => Number::formatMoney($invoice->balance, $this->service->config->client) ?: ' ']; $tbody[] = $element; } @@ -297,6 +388,7 @@ class PdfBuilder * * @param string $type "$product" or "$task" * @return array + * */ public function buildTableBody(string $type): array { @@ -369,7 +461,7 @@ class PdfBuilder } else { $_type = Str::startsWith($type, '$') ? ltrim($type, '$') : $type; - foreach ($this->context['pdf_variables']["{$_type}_columns"] as $key => $cell) { + foreach ($this->service->config->pdf_variables["{$_type}_columns"] as $key => $cell) { // We want to keep aliases like these: // $task.cost => $task.rate // $task.quantity => $task.hours @@ -404,8 +496,6 @@ class PdfBuilder return $elements; } - - /** * Formats the line items for display. * @@ -420,9 +510,6 @@ class PdfBuilder $data = []; - if (! is_array($items)) { - } - $locale_info = localeconv(); $this->service->config->entity_currency = $this->service->config->currency; @@ -448,8 +535,8 @@ class PdfBuilder $data[$key][$table_type.'.service'] = is_null(optional($item)->service) ? $item->product_key : $item->service; $currentDateTime = null; - if (isset($this->entity->next_send_date)) { - $currentDateTime = Carbon::parse($this->entity->next_send_date); + if (isset($this->service->config->entity->next_send_date)) { + $currentDateTime = Carbon::parse($this->service->config->entity->next_send_date); } $data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $this->service->config->currency_entity, $currentDateTime); @@ -531,6 +618,7 @@ class PdfBuilder * * @param string $type "product" or "task" * @return array + * */ public function buildTableHeader(string $type): array { @@ -546,7 +634,7 @@ class PdfBuilder '$task.rate' => '$task.cost', ]; - foreach ($this->context['pdf_variables']["{$type}_columns"] as $column) { + foreach ($this->service->config->pdf_variables["{$type}_columns"] as $column) { if (array_key_exists($column, $aliases)) { $elements[] = ['element' => 'th', 'content' => $aliases[$column] . '_label', 'properties' => ['data-ref' => "{$type}_table-" . substr($aliases[$column], 1) . '-th', 'hidden' => $this->service->config->settings_object->getSetting('hide_empty_columns_on_pdf')]]; } elseif ($column == '$product.discount' && !$this->service->company->enable_product_discount) { @@ -623,7 +711,13 @@ class PdfBuilder } } - + /** + * Generates the javascript block for + * hiding elements which need to be hidden + * + * @return array + * + */ public function sharedFooterElements(): array { // We want to show headers for statements, no exceptions. @@ -647,6 +741,13 @@ class PdfBuilder ]]; } + /** + * Generates the totals table for + * the product type entities + * + * @return self + * + */ private function getProductTotals(): self { @@ -660,6 +761,15 @@ class PdfBuilder return $this; } + /** + * Generates the entity details for + * Credits + * Quotes + * Invoices + * + * @return self + * + */ private function getProductEntityDetails(): self { @@ -701,7 +811,12 @@ class PdfBuilder } - /* Parent entry point when building sections of the design content */ + /** + * Parent entry point when building sections of the design content + * + * @return self + * + */ private function buildSections() :self { @@ -714,6 +829,12 @@ class PdfBuilder } + /** + * Generates the table totals for statements + * + * @return array + * + */ private function statementTableTotals(): array { return [ @@ -725,6 +846,14 @@ class PdfBuilder ]; } + /** + * Performs a variable check to ensure + * the variable exists + * + * @param string $variables + * @return bool + * + */ public function entityVariableCheck(string $variable): bool { // When it comes to invoice balance, we'll always show it. @@ -747,11 +876,11 @@ class PdfBuilder $_variable = $aliases[$variable]; } - if (is_null($this->entity->{$_variable})) { + if (is_null($this->service->config->entity->{$_variable})) { return true; } - if (empty($this->entity->{$_variable})) { + if (empty($this->service->config->entity->{$_variable})) { return true; } @@ -759,11 +888,17 @@ class PdfBuilder } //First pass done, need a second pass to abstract this content completely. + /** + * Builds the table totals for all entities, we'll want to split this + * + * @return array + * + */ public function getTableTotals() :array { //need to see where we don't pass all these particular variables. try and refactor thisout - $_variables = array_key_exists('variables', $this->context) - ? $this->context['variables'] + $_variables = array_key_exists('variables', $this->service->options) + ? $this->service->options['variables'] : ['values' => ['$this->service->config->entity.public_notes' => $this->service->config->entity->public_notes, '$this->service->config->entity.terms' => $this->service->config->entity->terms, '$this->service->config->entity_footer' => $this->service->config->entity->footer], 'labels' => []]; $variables = $this->service->config->pdf_variables['total_columns']; @@ -1287,25 +1422,169 @@ class PdfBuilder } +//////////////////////////////////////// + // Dom Traversal + /////////////// + public function getSectionNode(string $selector) + { + return $this->document->getElementById($selector); + } + public function updateElementProperties() :self + { + foreach ($this->sections as $element) { + if (isset($element['tag'])) { + $node = $this->document->getElementsByTagName($element['tag'])->item(0); + } elseif (! is_null($this->document->getElementById($element['id']))) { + $node = $this->document->getElementById($element['id']); + } else { + continue; + } - // if (isset($this->data['template']) && isset($this->data['variables'])) { - // $this->getEmptyElements($this->data['template'], $this->data['variables']); - // } + if (isset($element['properties'])) { + foreach ($element['properties'] as $property => $value) { + $this->updateElementProperty($node, $property, $value); + } + } - // if (isset($this->data['template'])) { - // $this->updateElementProperties($this->data['template']); - // } + if (isset($element['elements'])) { + $this->createElementContent($node, $element['elements']); + } + } - // if (isset($this->data['variables'])) { - // $this->updateVariables($this->data['variables']); - // } + return $this; + } - // return $this; + public function updateElementProperty($element, string $attribute, ?string $value) + { + // We have exception for "hidden" property. + // hidden="true" or hidden="false" will both hide the element, + // that's why we have to create an exception here for this rule. + if ($attribute == 'hidden' && ($value == false || $value == 'false')) { + return $element; + } + $element->setAttribute($attribute, $value); + + if ($element->getAttribute($attribute) === $value) { + return $element; + } + + return $element; + } + + public function createElementContent($element, $children) :self + { + foreach ($children as $child) { + $contains_html = false; + + if ($child['element'] !== 'script') { + if (array_key_exists('process_markdown', $this->data) && array_key_exists('content', $child) && $this->data['process_markdown']) { + $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; + } + + public function updateVariables() + { + $html = strtr($this->getCompiledHTML(), $this->service->config->html_variables['labels']); + + $html = strtr($html, $this->service->config->html_variables['values']); + + @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + + $this->document->saveHTML(); + + return $this; + } + + public function updateVariable(string $element, string $variable, string $value) + { + $element = $this->document->getElementById($element); + + $original = $element->nodeValue; + + $element->nodeValue = ''; + + $replaced = strtr($original, [$variable => $value]); + + $element->appendChild( + $this->document->createTextNode($replaced) + ); + + return $element; + } + + public function getEmptyElements() :self + { + foreach ($this->sections as $element) { + if (isset($element['elements'])) { + $this->getEmptyChildrens($element['elements'], $this->service->config->html_variables); + } + } + + return $this; + } + + public function getEmptyChildrens(array $children) + { + foreach ($children as $key => $child) { + if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { + $value = strtr($child['content'], $this->service->config->html_variables['values']); + if ($value === '' || $value === ' ') { + $child['is_empty'] = true; + } + } + + if (isset($child['elements'])) { + $this->getEmptyChildrens($child['elements']); + } + } + + return $this; + } - } \ No newline at end of file diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 08fd10fe2777..61fe0acccba9 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -100,7 +100,7 @@ class PdfConfiguration { $default = (array) CompanySettings::getEntityVariableDefaults(); - $variables = $this->service->company->pdf_variables; + $variables = (array)$this->service->company->settings->pdf_variables; foreach ($default as $property => $value) { if (array_key_exists($property, $variables)) { From ca853d29e5e72b268c1b3ecc4db9ad8d70516e62 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 28 Dec 2022 21:28:58 +1100 Subject: [PATCH 08/36] Pdf Service Refactor --- app/Services/Pdf/PdfBuilder.php | 89 ++++++++++++++++++-------------- app/Services/Pdf/PdfDesigner.php | 13 +++-- app/Services/Pdf/PdfService.php | 4 +- tests/Pdf/PdfServiceTest.php | 14 +++++ 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 30aae5471bcc..0dcac235bd5c 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -52,7 +52,11 @@ class PdfBuilder { $this->getTemplate() - ->buildSections() + ->buildSections(); + + nlog($this->sections); + + $this ->getEmptyElements() ->updateElementProperties() ->updateVariables(); @@ -85,9 +89,11 @@ class PdfBuilder $document = new DOMDocument(); $document->validateOnParse = true; - @$document->loadHTML(mb_convert_encoding($this->service->config->designer->template, 'HTML-ENTITIES', 'UTF-8')); + + @$document->loadHTML(mb_convert_encoding($this->service->designer->template, 'HTML-ENTITIES', 'UTF-8')); $this->document = $document; + $this->xpath = new DOMXPath($document); return $this; @@ -112,6 +118,13 @@ class PdfBuilder } + private function mergeSections(array $section) :self + { + $this->sections = array_merge($this->sections, $section); + + return $this; + } + /** * Generates delivery note sections * @@ -124,7 +137,7 @@ class PdfBuilder $this->genericSectionBuilder() ->getProductTotals(); - $this->sections[] = [ + $this->mergeSections([ 'client-details' => [ 'id' => 'client-details', 'elements' => $this->clientDeliveryDetails(), @@ -137,7 +150,7 @@ class PdfBuilder 'id' => 'entity-details', 'elements' => $this->deliveryNoteDetails(), ], - ]; + ]); return $this; @@ -154,7 +167,7 @@ class PdfBuilder $this->genericSectionBuilder(); - $this->sections[] = [ + $this->mergeSections( [ 'statement-invoice-table' => [ 'id' => 'statement-invoice-table', 'elements' => $this->statementInvoiceTable(), @@ -179,7 +192,7 @@ class PdfBuilder 'id' => 'table-totals', 'elements' => $this->statementTableTotals(), ], - ]; + ]); return $this; @@ -308,7 +321,7 @@ class PdfBuilder $this->genericSectionBuilder() ->getProductTotals(); - $this->sections[] = [ + $this->mergeSections([ 'vendor-details' => [ 'id' => 'vendor-details', 'elements' => $this->vendorDetails(), @@ -317,7 +330,7 @@ class PdfBuilder 'id' => 'entity-details', 'elements' => $this->purchaseOrderDetails(), ], - ]; + ]); return $this; @@ -333,7 +346,7 @@ class PdfBuilder private function genericSectionBuilder(): self { - $this->sections[] = [ + $this->mergeSections([ 'company-details' => [ 'id' => 'company-details', 'elements' => $this->companyDetails(), @@ -348,7 +361,7 @@ class PdfBuilder $this->sharedFooterElements(), ], ], - ]; + ]); return $this; } @@ -394,7 +407,7 @@ class PdfBuilder { $elements = []; - $items = $this->transformLineItems($this->entity->line_items, $type); + $items = $this->transformLineItems($this->service->config->entity->line_items, $type); $this->processNewLines($items); @@ -436,12 +449,12 @@ class PdfBuilder $element = ['element' => 'tr', 'elements' => []]; if ( - array_key_exists($type, $this->context) && - !empty($this->context[$type]) && - !is_null($this->context[$type]) + array_key_exists($type, $this->service->options) && + !empty($this->service->options[$type]) && + !is_null($this->service->options[$type]) ) { $document = new DOMDocument(); - $document->loadHTML($this->context[$type], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); + $document->loadHTML($this->service->options[$type], LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $td = $document->getElementsByTagName('tr')->item(0); @@ -751,12 +764,12 @@ class PdfBuilder private function getProductTotals(): self { - $this->sections[] = [ + $this->mergeSections([ 'table-totals' => [ 'id' => 'table-totals', 'elements' => $this->getTableTotals(), ], - ]; + ]); return $this; } @@ -776,33 +789,33 @@ class PdfBuilder if($this->service->config->entity_string == 'invoice') { - $this->sections[] = [ + $this->mergeSections( [ 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->invoiceDetails(), ], - ]; + ]); } elseif($this->service->config->entity_string == 'quote') { - $this->sections[] = [ + $this->mergeSections( [ 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->quoteDetails(), ], - ]; + ]); } elseif($this->service->config->entity_string == 'credit') { - $this->sections[] = [ + $this->mergeSections( [ 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->creditDetails(), ], - ]; + ]); } @@ -820,8 +833,8 @@ class PdfBuilder private function buildSections() :self { - return match ($this->service->config->document_type) { - PdfService::PRODUCT => $this->getProductSections, + return match ($this->service->document_type) { + PdfService::PRODUCT => $this->getProductSections(), PdfService::DELIVERY_NOTE => $this->getDeliveryNoteSections(), PdfService::STATEMENT => $this->getStatementSections(), PdfService::PURCHASE_ORDER => $this->getPurchaseOrderSections(), @@ -919,7 +932,7 @@ class PdfBuilder ]; - if ($this->service->config->document_type == PdfService::DELIVERY_NOTE) { + if ($this->service->document_type == PdfService::DELIVERY_NOTE) { return $elements; } @@ -1028,7 +1041,7 @@ class PdfBuilder public function getProductAndTaskTables(): self { - $this->sections[] = [ + $this->mergeSections( [ 'product-table' => [ 'id' => 'product-table', 'elements' => $this->productTable(), @@ -1037,7 +1050,7 @@ class PdfBuilder 'id' => 'task-table', 'elements' => $this->taskTable(), ], - ]; + ]); return $this; } @@ -1050,12 +1063,12 @@ class PdfBuilder */ public function getClientDetails(): self { - $this->sections[] = [ + $this->mergeSections( [ 'client-details' => [ 'id' => 'client-details', 'elements' => $this->clientDetails(), ], - ]; + ]); return $this; } @@ -1317,7 +1330,7 @@ class PdfBuilder ['element' => 'th', 'content' => '$product.quantity_label', 'properties' => ['data-ref' => 'delivery_note-product.quantity_label']], ]; - $items = $this->transformLineItems($this->service->config->entity->line_items, $this->service->config->document_type); + $items = $this->transformLineItems($this->service->config->entity->line_items, $this->service->document_type); $this->processNewLines($items); @@ -1422,9 +1435,9 @@ class PdfBuilder } -//////////////////////////////////////// + //////////////////////////////////////// // Dom Traversal - /////////////// + /////////////////////////////////////// public function getSectionNode(string $selector) @@ -1482,7 +1495,7 @@ class PdfBuilder $contains_html = false; if ($child['element'] !== 'script') { - if (array_key_exists('process_markdown', $this->data) && array_key_exists('content', $child) && $this->data['process_markdown']) { + if (array_key_exists('process_markdown', $this->service->options) && array_key_exists('content', $child) && $this->service->options['process_markdown']) { $child['content'] = str_replace('
', "\r", $child['content']); $child['content'] = $this->commonmark->convert($child['content'] ?? ''); } @@ -1530,9 +1543,9 @@ class PdfBuilder public function updateVariables() { - $html = strtr($this->getCompiledHTML(), $this->service->config->html_variables['labels']); + $html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']); - $html = strtr($html, $this->service->config->html_variables['values']); + $html = strtr($html, $this->service->html_variables['values']); @$this->document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); @@ -1562,7 +1575,7 @@ class PdfBuilder { foreach ($this->sections as $element) { if (isset($element['elements'])) { - $this->getEmptyChildrens($element['elements'], $this->service->config->html_variables); + $this->getEmptyChildrens($element['elements'], $this->service->html_variables); } } @@ -1573,7 +1586,7 @@ class PdfBuilder { foreach ($children as $key => $child) { if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { - $value = strtr($child['content'], $this->service->config->html_variables['values']); + $value = strtr($child['content'], $this->service->html_variables['values']); if ($value === '' || $value === ' ') { $child['is_empty'] = true; } diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php index 0f7cf9671226..3b5bc6321a35 100644 --- a/app/Services/Pdf/PdfDesigner.php +++ b/app/Services/Pdf/PdfDesigner.php @@ -61,6 +61,15 @@ class PdfDesigner /** * If the user has implemented a custom design, then we need to rebuild the design at this point */ + + /** + * Returns the custom HTML design as + * a string + * + * @param array + * @return string + * + */ private function composeFromPartials(array $partials) :string { $html = ''; @@ -72,9 +81,5 @@ class PdfDesigner return $html; } - - - - } \ No newline at end of file diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 51e7d0cf9400..205c60c76c87 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -69,6 +69,8 @@ class PdfService { $this->builder->build(); + return $this; + } public function getPdf() @@ -78,7 +80,7 @@ class PdfService public function getHtml() { - + return $this->builder->getCompiledHTML(); } diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index b4d39ba936b4..2ff72370ba38 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -33,6 +33,19 @@ class PdfServiceTest extends TestCase $this->makeTestData(); } + public function testHtmlGeneration() + { + + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertIsString($service->build()->getHtml()); + + nlog($service->build()->getHtml()); + + } + public function testInitOfClass() { @@ -85,4 +98,5 @@ class PdfServiceTest extends TestCase $this->assertIsString($service->designer->template); } + } \ No newline at end of file From 3ec7f6a80d3d7dffb5138707b7f45390c0c2ea3e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Dec 2022 01:50:11 +1100 Subject: [PATCH 09/36] Refactor for PDF Generation --- app/Jobs/Entity/CreateEntityPdf.php | 178 ++++++-------------------- app/Services/Pdf/PdfBuilder.php | 81 ++++++++++-- app/Services/Pdf/PdfConfiguration.php | 32 ++++- app/Services/Pdf/PdfService.php | 116 +++++++++++------ tests/Pdf/PdfServiceTest.php | 15 ++- 5 files changed, 227 insertions(+), 195 deletions(-) diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php index 0f388ade0b3c..60ec71508a04 100644 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ b/app/Jobs/Entity/CreateEntityPdf.php @@ -24,6 +24,7 @@ use App\Models\Quote; use App\Models\QuoteInvitation; use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; +use App\Services\Pdf\PdfService; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; @@ -49,22 +50,14 @@ use setasign\Fpdi\PdfParser\StreamReader; class CreateEntityPdf implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $entity; - public $company; - - public $contact; - private $disk; public $invitation; - public $entity_string = ''; - - public $client; - /** * Create a new job instance. * @@ -72,145 +65,57 @@ class CreateEntityPdf implements ShouldQueue */ public function __construct($invitation, $disk = null) { + $this->invitation = $invitation; - if ($invitation instanceof InvoiceInvitation) { - // $invitation->load('contact.client.company','invoice.client','invoice.user.account'); - $this->entity = $invitation->invoice; - $this->entity_string = 'invoice'; - } elseif ($invitation instanceof QuoteInvitation) { - // $invitation->load('contact.client.company','quote.client','quote.user.account'); - $this->entity = $invitation->quote; - $this->entity_string = 'quote'; - } elseif ($invitation instanceof CreditInvitation) { - // $invitation->load('contact.client.company','credit.client','credit.user.account'); - $this->entity = $invitation->credit; - $this->entity_string = 'credit'; - } elseif ($invitation instanceof RecurringInvoiceInvitation) { - // $invitation->load('contact.client.company','recurring_invoice'); - $this->entity = $invitation->recurring_invoice; - $this->entity_string = 'recurring_invoice'; - } - - $this->company = $invitation->company; - - $this->contact = $invitation->contact; - - $this->client = $invitation->contact->client; - $this->client->load('company'); - $this->disk = $disk ?? config('filesystems.default'); + } public function handle() { - MultiDB::setDb($this->company->db); - /* Forget the singleton*/ - App::forgetInstance('translator'); + $starttime = microtime(true); - /* Init a new copy of the translator*/ - $t = app('translator'); - - /* Set the locale*/ - App::setLocale($this->client->locale()); + MultiDB::setDb($this->invitation->company->db); - /* Set customized translations _NOW_ */ - $t->replace(Ninja::transformTranslations($this->client->getMergedSettings())); + if ($this->invitation instanceof InvoiceInvitation) { + $this->entity = $this->invitation->invoice; + $path = $this->invitation->contact->client->invoice_filepath($this->invitation); + } elseif ($this->invitation instanceof QuoteInvitation) { + $this->entity = $this->invitation->quote; + $path = $this->invitation->contact->client->quote_filepath($this->invitation); + } elseif ($this->invitation instanceof CreditInvitation) { + $this->entity = $this->invitation->credit; + $path = $this->invitation->contact->client->credit_filepath($this->invitation); + } elseif ($this->invitation instanceof RecurringInvoiceInvitation) { + $this->entity = $this->invitation->recurring_invoice; + $path = $this->invitation->contact->client->recurring_invoice_filepath($this->invitation); - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->generate($this->invitation); - } - - $entity_design_id = ''; - - if ($this->entity instanceof Invoice) { - $path = $this->client->invoice_filepath($this->invitation); - $entity_design_id = 'invoice_design_id'; - } elseif ($this->entity instanceof Quote) { - $path = $this->client->quote_filepath($this->invitation); - $entity_design_id = 'quote_design_id'; - } elseif ($this->entity instanceof Credit) { - $path = $this->client->credit_filepath($this->invitation); - $entity_design_id = 'credit_design_id'; - } elseif ($this->entity instanceof RecurringInvoice) { - $path = $this->client->recurring_invoice_filepath($this->invitation); - $entity_design_id = 'invoice_design_id'; } $file_path = $path.$this->entity->numberFormatter().'.pdf'; - $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->client->getSetting($entity_design_id)); - - $design = Design::find($entity_design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if (! $design) { - $design = Design::find(2); - } - - $html = new HtmlEngine($this->invitation); - - if ($design->is_custom) { - $options = [ - 'custom_partials' => json_decode(json_encode($design->design), true), - ]; - $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => $this->client, - 'entity' => $this->entity, - 'pdf_variables' => (array) $this->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), - 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), - ], - 'process_markdown' => $this->entity->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - $pdf = null; - - try { - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); - } - - if (config('ninja.log_pdf_html')) { - info($maker->getCompiledHTML()); - } + // $state = [ + // 'template' => $template->elements([ + // 'client' => $this->client, + // 'entity' => $this->entity, + // 'pdf_variables' => (array) $this->company->settings->pdf_variables, + // '$product' => $design->design->product, + // 'variables' => $variables, + // ]), + // 'variables' => $variables, + // 'options' => [ + // 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), + // 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), + // ], + // 'process_markdown' => $this->entity->client->company->markdown_enabled, + // ]; + + $pdf = (new PdfService($this->invitation))->getPdf(); + + $endtime = microtime(true); + nlog($endtime - $starttime); if ($pdf) { try { @@ -220,13 +125,6 @@ class CreateEntityPdf implements ShouldQueue } } - $this->invitation = null; - $this->entity = null; - $this->company = null; - $this->client = null; - $this->contact = null; - $maker = null; - $state = null; return $file_path; } diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 0dcac235bd5c..8f8f5582ca68 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -20,6 +20,7 @@ use DOMDocument; use DOMXPath; use Illuminate\Support\Carbon; use Illuminate\Support\Str; +use League\CommonMark\CommonMarkConverter; class PdfBuilder { @@ -27,19 +28,35 @@ class PdfBuilder public PdfService $service; + private CommonMarkConverter $commonmark; + /** * an array of sections to be injected into the template + * * @var array */ public array $sections = []; + /** + * The DOM Document; + * + * @var $document + */ + public DomDocument $document; + /** * @param PdfService $service * @return void */ public function __construct(PdfService $service) { + $this->service = $service; + + $this->commonmark = new CommonMarkConverter([ + 'allow_unsafe_links' => false, + ]); + } /** @@ -52,11 +69,7 @@ class PdfBuilder { $this->getTemplate() - ->buildSections(); - - nlog($this->sections); - - $this + ->buildSections() ->getEmptyElements() ->updateElementProperties() ->updateVariables(); @@ -97,6 +110,7 @@ class PdfBuilder $this->xpath = new DOMXPath($document); return $this; + } /** @@ -120,9 +134,11 @@ class PdfBuilder private function mergeSections(array $section) :self { + $this->sections = array_merge($this->sections, $section); return $this; + } /** @@ -212,6 +228,7 @@ class PdfBuilder return [ ['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->service->config->client)], ]; + } @@ -222,6 +239,7 @@ class PdfBuilder */ public function statementPaymentTable(): array { + if (is_null($this->service->option['payments'])) { return []; } @@ -255,6 +273,7 @@ class PdfBuilder ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')], ['element' => 'tbody', 'elements' => $tbody], ]; + } /** @@ -666,6 +685,7 @@ class PdfBuilder } return $elements; + } /** @@ -680,6 +700,7 @@ class PdfBuilder */ public function processTaxColumns(string $type): void { + if ($type == 'product') { $type_id = 1; } @@ -722,6 +743,7 @@ class PdfBuilder array_splice($this->service->config->pdf_variables["{$type}_columns"], $key, 1, $taxes); } } + } /** @@ -752,6 +774,7 @@ class PdfBuilder ['element' => 'script', 'content' => $javascript], ['element' => 'script', 'content' => $html_decode], ]]; + } /** @@ -772,6 +795,7 @@ class PdfBuilder ]); return $this; + } /** @@ -786,7 +810,6 @@ class PdfBuilder private function getProductEntityDetails(): self { - if($this->service->config->entity_string == 'invoice') { $this->mergeSections( [ @@ -821,7 +844,6 @@ class PdfBuilder return $this; - } /** @@ -850,6 +872,7 @@ class PdfBuilder */ private function statementTableTotals(): array { + return [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: block; align-items: flex-start; page-break-inside: avoid; visible !important;'], 'elements' => [ @@ -857,6 +880,7 @@ class PdfBuilder ]], ]], ]; + } /** @@ -898,6 +922,7 @@ class PdfBuilder } return false; + } //First pass done, need a second pass to abstract this content completely. @@ -1053,6 +1078,7 @@ class PdfBuilder ]); return $this; + } /** @@ -1063,6 +1089,7 @@ class PdfBuilder */ public function getClientDetails(): self { + $this->mergeSections( [ 'client-details' => [ 'id' => 'client-details', @@ -1071,6 +1098,7 @@ class PdfBuilder ]); return $this; + } /** @@ -1080,6 +1108,7 @@ class PdfBuilder */ public function productTable(): array { + $product_items = collect($this->service->config->entity->line_items)->filter(function ($item) { return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5; }); @@ -1092,6 +1121,7 @@ class PdfBuilder ['element' => 'thead', 'elements' => $this->buildTableHeader('product')], ['element' => 'tbody', 'elements' => $this->buildTableBody('$product')], ]; + } /** @@ -1101,6 +1131,7 @@ class PdfBuilder */ public function taskTable(): array { + $task_items = collect($this->service->config->entity->line_items)->filter(function ($item) { return $item->type_id == 2; }); @@ -1113,6 +1144,7 @@ class PdfBuilder ['element' => 'thead', 'elements' => $this->buildTableHeader('task')], ['element' => 'tbody', 'elements' => $this->buildTableBody('$task')], ]; + } @@ -1156,6 +1188,7 @@ class PdfBuilder $variables = $this->service->config->pdf_variables['invoice_details']; return $this->genericDetailsBuilder($variables); + } /** @@ -1166,6 +1199,7 @@ class PdfBuilder */ public function quoteDetails(): array { + $variables = $this->service->config->pdf_variables['quote_details']; if ($this->service->config->entity->partial > 0) { @@ -1173,6 +1207,7 @@ class PdfBuilder } return $this->genericDetailsBuilder($variables); + } @@ -1188,6 +1223,7 @@ class PdfBuilder $variables = $this->service->config->pdf_variables['credit_details']; return $this->genericDetailsBuilder($variables); + } /** @@ -1220,6 +1256,7 @@ class PdfBuilder }); return $this->genericDetailsBuilder($variables); + } /** @@ -1255,6 +1292,7 @@ class PdfBuilder } return $elements; + } @@ -1301,6 +1339,7 @@ class PdfBuilder */ public function clientDetails(): array { + $elements = []; if(!$this->service->config->client) @@ -1313,6 +1352,7 @@ class PdfBuilder } return $elements; + } /** @@ -1323,7 +1363,6 @@ class PdfBuilder public function deliveryNoteTable(): array { /* Static array of delivery note columns*/ - $thead = [ ['element' => 'th', 'content' => '$item_label', 'properties' => ['data-ref' => 'delivery_note-item_label']], ['element' => 'th', 'content' => '$description_label', 'properties' => ['data-ref' => 'delivery_note-description_label']], @@ -1354,6 +1393,7 @@ class PdfBuilder ['element' => 'thead', 'elements' => $thead], ['element' => 'tbody', 'elements' => $this->buildTableBody(PdfService::DELIVERY_NOTE)], ]; + } /** @@ -1366,6 +1406,7 @@ class PdfBuilder */ public function processNewLines(array &$items): void { + foreach ($items as $key => $item) { foreach ($item as $variable => $value) { $item[$variable] = str_replace("\n", '
', $value); @@ -1373,6 +1414,7 @@ class PdfBuilder $items[$key] = $item; } + } /** @@ -1383,6 +1425,7 @@ class PdfBuilder */ public function companyDetails(): array { + $variables = $this->service->config->pdf_variables['company_details']; $elements = []; @@ -1392,6 +1435,7 @@ class PdfBuilder } return $elements; + } /** @@ -1403,6 +1447,7 @@ class PdfBuilder */ public function companyAddress(): array { + $variables = $this->service->config->pdf_variables['company_address']; $elements = []; @@ -1412,6 +1457,7 @@ class PdfBuilder } return $elements; + } /** @@ -1423,6 +1469,7 @@ class PdfBuilder */ public function vendorDetails(): array { + $elements = []; $variables = $this->service->config->pdf_variables['vendor_details']; @@ -1432,6 +1479,7 @@ class PdfBuilder } return $elements; + } @@ -1442,11 +1490,14 @@ class PdfBuilder public function getSectionNode(string $selector) { + return $this->document->getElementById($selector); + } public function updateElementProperties() :self { + foreach ($this->sections as $element) { if (isset($element['tag'])) { $node = $this->document->getElementsByTagName($element['tag'])->item(0); @@ -1468,6 +1519,7 @@ class PdfBuilder } return $this; + } public function updateElementProperty($element, string $attribute, ?string $value) @@ -1487,15 +1539,17 @@ class PdfBuilder } return $element; + } public function createElementContent($element, $children) :self { + foreach ($children as $child) { $contains_html = false; if ($child['element'] !== 'script') { - if (array_key_exists('process_markdown', $this->service->options) && array_key_exists('content', $child) && $this->service->options['process_markdown']) { + if ($this->service->company->markdown_enabled && array_key_exists('content', $child)) { $child['content'] = str_replace('
', "\r", $child['content']); $child['content'] = $this->commonmark->convert($child['content'] ?? ''); } @@ -1539,10 +1593,12 @@ class PdfBuilder } return $this; + } public function updateVariables() { + $html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']); $html = strtr($html, $this->service->html_variables['values']); @@ -1552,10 +1608,12 @@ class PdfBuilder $this->document->saveHTML(); return $this; + } public function updateVariable(string $element, string $variable, string $value) { + $element = $this->document->getElementById($element); $original = $element->nodeValue; @@ -1569,10 +1627,12 @@ class PdfBuilder ); return $element; + } public function getEmptyElements() :self { + foreach ($this->sections as $element) { if (isset($element['elements'])) { $this->getEmptyChildrens($element['elements'], $this->service->html_variables); @@ -1580,10 +1640,12 @@ class PdfBuilder } return $this; + } public function getEmptyChildrens(array $children) { + foreach ($children as $key => $child) { if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { $value = strtr($child['content'], $this->service->html_variables['values']); @@ -1598,6 +1660,7 @@ class PdfBuilder } return $this; + } } \ No newline at end of file diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 61fe0acccba9..9b908a487d6a 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -29,7 +29,9 @@ use App\Models\RecurringInvoiceInvitation; use App\Models\Vendor; use App\Models\VendorContact; use App\Services\Pdf\PdfService; +use App\Utils\Ninja; use App\Utils\Traits\MakesHash; +use Illuminate\Support\Facades\App; class PdfConfiguration { @@ -61,7 +63,9 @@ class PdfConfiguration /** * The parent object of the currency + * * @var App\Models\Client | App\Models\Vendor + * */ public $currency_entity; @@ -72,20 +76,36 @@ class PdfConfiguration } - public function init() + public function init(): self { $this->setEntityType() ->setEntityProperties() ->setPdfVariables() ->setDesign() - ->setCurrency(); + ->setCurrency() + ->setLocale(); return $this; } - private function setCurrency() :self + private function setLocale(): self + { + + App::forgetInstance('translator'); + + $t = app('translator'); + + App::setLocale($this->settings_object->locale()); + + $t->replace(Ninja::transformTranslations($this->settings)); + + return $this; + + } + + private function setCurrency(): self { $this->currency = $this->client ? $this->client->currency() : $this->vendor->currency(); @@ -100,6 +120,7 @@ class PdfConfiguration { $default = (array) CompanySettings::getEntityVariableDefaults(); + $variables = (array)$this->service->company->settings->pdf_variables; foreach ($default as $property => $value) { @@ -139,11 +160,13 @@ class PdfConfiguration } return $this; + } private function setEntityProperties() { - $entity_design_id = ''; + + $entity_design_id = ''; if ($this->entity instanceof Invoice) { @@ -198,6 +221,7 @@ class PdfConfiguration $this->path = $this->path.$this->entity->numberFormatter().'.pdf'; return $this; + } private function setDesign() diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 205c60c76c87..7ee6c2511ee9 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -12,13 +12,19 @@ namespace App\Services\Pdf; use App\Models\Account; -use App\Services\Pdf\PdfConfiguration; -use App\Utils\HtmlEngine; use App\Models\Company; +use App\Services\Pdf\PdfConfiguration; +use App\Utils\HostedPDF\NinjaPdf; +use App\Utils\HtmlEngine; +use App\Utils\PhantomJS\Phantom; +use App\Utils\Traits\Pdf\PageNumbering; +use App\Utils\Traits\Pdf\PdfMaker; class PdfService { + use PdfMaker, PageNumbering; + public $invitation; public Company $company; @@ -55,58 +61,90 @@ class PdfService $this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); - $this->builder = (new PdfBuilder($this)); - $this->designer = (new PdfDesigner($this))->build(); $this->document_type = $document_type; $this->options = $options; - } - - public function build() - { - $this->builder->build(); - - return $this; + $this->builder = (new PdfBuilder($this))->build(); } + /** + * Resolves the PDF generation type and + * attempts to generate a PDF from the HTML + * string. + * + * @return mixed + * + */ public function getPdf() { + try { + + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') + { + + $pdf = (new Phantom)->convertHtmlToPdf($this->getHtml()); + + } + elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') + { + + $pdf = (new NinjaPdf())->build($this->getHtml()); + + } + else + { + + $pdf = $this->makePdf(null, null, $this->getHtml()); + + } + + $numbered_pdf = $this->pageNumbering($pdf, $this->company); + + if ($numbered_pdf) + { + + $pdf = $numbered_pdf; + + } + + + } catch (\Exception $e) { + + nlog(print_r($e->getMessage(), 1)); + + throw new \Exception($e->getMessage(), $e->getCode()); + + } + + return $pdf; + } - public function getHtml() + /** + * Renders the dom document to HTML + * + * @return string + * + */ + public function getHtml(): string { - return $this->builder->getCompiledHTML(); + + $html = $this->builder->getCompiledHTML(); + + if (config('ninja.log_pdf_html')) + { + + info($html); + + } + + return $html; + } - - // $state = [ - // 'template' => $template->elements([ - // 'client' => $this->client, - // 'entity' => $this->entity, - // 'pdf_variables' => (array) $this->company->settings->pdf_variables, - // '$product' => $design->design->product, - // 'variables' => $variables, - // ]), - // 'variables' => $variables, - // 'options' => [ - // 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), - // 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), - // ], - // 'process_markdown' => $this->entity->client->company->markdown_enabled, - // ]; - - // $maker = new PdfMakerService($state); - - // $maker - // ->design($template) - // ->build(); - - - - } \ No newline at end of file diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php index 2ff72370ba38..d892bcc4c779 100644 --- a/tests/Pdf/PdfServiceTest.php +++ b/tests/Pdf/PdfServiceTest.php @@ -33,6 +33,17 @@ class PdfServiceTest extends TestCase $this->makeTestData(); } + public function testPdfGeneration() + { + + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertNotNull($service->getPdf()); + + } + public function testHtmlGeneration() { @@ -40,9 +51,7 @@ class PdfServiceTest extends TestCase $service = new PdfService($invitation); - $this->assertIsString($service->build()->getHtml()); - - nlog($service->build()->getHtml()); + $this->assertIsString($service->getHtml()); } From adb6980c0ab65262837a81be55cc95cfa998e437 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Dec 2022 02:13:44 +1100 Subject: [PATCH 10/36] Refactor for PDF Generation --- app/Jobs/Entity/CreateRawPdf.php | 147 +----------------- app/Services/Invoice/GenerateDeliveryNote.php | 2 +- 2 files changed, 5 insertions(+), 144 deletions(-) diff --git a/app/Jobs/Entity/CreateRawPdf.php b/app/Jobs/Entity/CreateRawPdf.php index 178ab18f97ad..5914c5684e23 100644 --- a/app/Jobs/Entity/CreateRawPdf.php +++ b/app/Jobs/Entity/CreateRawPdf.php @@ -26,6 +26,7 @@ use App\Models\RecurringInvoiceInvitation; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; +use App\Services\Pdf\PdfService; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\Ninja; @@ -46,18 +47,10 @@ use Illuminate\Support\Facades\Storage; class CreateRawPdf implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; - - public $entity; - - public $company; - - public $contact; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $invitation; - public $entity_string = ''; - /** * Create a new job instance. * @@ -69,147 +62,15 @@ class CreateRawPdf implements ShouldQueue $this->invitation = $invitation; - if ($invitation instanceof InvoiceInvitation) { - $this->entity = $invitation->invoice; - $this->entity_string = 'invoice'; - } elseif ($invitation instanceof QuoteInvitation) { - $this->entity = $invitation->quote; - $this->entity_string = 'quote'; - } elseif ($invitation instanceof CreditInvitation) { - $this->entity = $invitation->credit; - $this->entity_string = 'credit'; - } elseif ($invitation instanceof RecurringInvoiceInvitation) { - $this->entity = $invitation->recurring_invoice; - $this->entity_string = 'recurring_invoice'; - } - - $this->company = $invitation->company; - - $this->contact = $invitation->contact; } public function handle() { - /* Forget the singleton*/ - App::forgetInstance('translator'); + $pdf = (new PdfService($this->invitation))->getPdf(); - /* Init a new copy of the translator*/ - $t = app('translator'); - /* Set the locale*/ - App::setLocale($this->contact->preferredLocale()); + return $pdf; - /* Set customized translations _NOW_ */ - $t->replace(Ninja::transformTranslations($this->entity->client->getMergedSettings())); - - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->generate($this->invitation, true); - } - - $entity_design_id = ''; - - if ($this->entity instanceof Invoice) { - $path = $this->entity->client->invoice_filepath($this->invitation); - $entity_design_id = 'invoice_design_id'; - } elseif ($this->entity instanceof Quote) { - $path = $this->entity->client->quote_filepath($this->invitation); - $entity_design_id = 'quote_design_id'; - } elseif ($this->entity instanceof Credit) { - $path = $this->entity->client->credit_filepath($this->invitation); - $entity_design_id = 'credit_design_id'; - } elseif ($this->entity instanceof RecurringInvoice) { - $path = $this->entity->client->recurring_invoice_filepath($this->invitation); - $entity_design_id = 'invoice_design_id'; - } - - $file_path = $path.$this->entity->numberFormatter().'.pdf'; - - $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey($this->entity->client->getSetting($entity_design_id)); - - $design = Design::find($entity_design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if (! $design) { - $design = Design::find(2); - } - - $html = new HtmlEngine($this->invitation); - - if ($design->is_custom) { - $options = [ - 'custom_partials' => json_decode(json_encode($design->design), true), - ]; - $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => $this->entity->client, - 'entity' => $this->entity, - 'pdf_variables' => (array) $this->entity->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), - 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), - ], - 'process_markdown' => $this->entity->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - $pdf = null; - - try { - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $finfo = new \finfo(FILEINFO_MIME); - - //fallback in case hosted PDF fails. - if ($finfo->buffer($pdf) != 'application/pdf; charset=binary') { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if ($numbered_pdf) { - $pdf = $numbered_pdf; - } - } - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); - } - - if (config('ninja.log_pdf_html')) { - info($maker->getCompiledHTML()); - } - - if ($pdf) { - $maker =null; - $state = null; - return $pdf; - } - - throw new FilePermissionsFailure('Unable to generate the raw PDF'); } public function failed($e) diff --git a/app/Services/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index f60c9cd8766c..255d7e06e59f 100644 --- a/app/Services/Invoice/GenerateDeliveryNote.php +++ b/app/Services/Invoice/GenerateDeliveryNote.php @@ -59,7 +59,7 @@ class GenerateDeliveryNote : $this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id')); $invitation = $this->invoice->invitations->first(); - // $file_path = sprintf('%s%s_delivery_note.pdf', $this->invoice->client->invoice_filepath($invitation), $this->invoice->number); + $file_path = sprintf('%sdelivery_note.pdf', $this->invoice->client->invoice_filepath($invitation)); if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { From dd070747cfac5722388274ad10f3ca90dce25d1b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 29 Dec 2022 02:22:48 +1100 Subject: [PATCH 11/36] Fixes for delivery notes --- app/Services/Invoice/GenerateDeliveryNote.php | 51 ++----------------- app/Services/Pdf/PdfBuilder.php | 8 +-- 2 files changed, 7 insertions(+), 52 deletions(-) diff --git a/app/Services/Invoice/GenerateDeliveryNote.php b/app/Services/Invoice/GenerateDeliveryNote.php index 255d7e06e59f..108f69e01e7e 100644 --- a/app/Services/Invoice/GenerateDeliveryNote.php +++ b/app/Services/Invoice/GenerateDeliveryNote.php @@ -15,6 +15,7 @@ namespace App\Services\Invoice; use App\Models\ClientContact; use App\Models\Design; use App\Models\Invoice; +use App\Services\Pdf\PdfService; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; use App\Utils\HostedPDF\NinjaPdf; @@ -54,62 +55,16 @@ class GenerateDeliveryNote public function run() { - $design_id = $this->invoice->design_id - ? $this->invoice->design_id - : $this->decodePrimaryKey($this->invoice->client->getSetting('invoice_design_id')); $invitation = $this->invoice->invitations->first(); $file_path = sprintf('%sdelivery_note.pdf', $this->invoice->client->invoice_filepath($invitation)); - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->generate($this->invoice->invitations->first()); - } - - $design = Design::find($design_id); - $html = new HtmlEngine($invitation); - - if ($design->is_custom) { - $options = ['custom_partials' => json_decode(json_encode($design->design), true)]; - $template = new PdfMakerDesign(PdfMakerDesign::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - - $state = [ - 'template' => $template->elements([ - 'client' => $this->invoice->client, - 'entity' => $this->invoice, - 'pdf_variables' => (array) $this->invoice->company->settings->pdf_variables, - 'contact' => $this->contact, - ], 'delivery_note'), - 'variables' => $html->generateLabelsAndValues(), - 'process_markdown' => $this->invoice->client->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - // Storage::makeDirectory($this->invoice->client->invoice_filepath(), 0775); - - if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - } else { - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML()); - } - - if (config('ninja.log_pdf_html')) { - info($maker->getCompiledHTML()); - } + $pdf = (new PdfService($invitation, 'delivery_note'))->getPdf(); Storage::disk($this->disk)->put($file_path, $pdf); - - $maker = null; - $state = null; return $file_path; + } } diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 8f8f5582ca68..0179456581a8 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -937,16 +937,16 @@ class PdfBuilder //need to see where we don't pass all these particular variables. try and refactor thisout $_variables = array_key_exists('variables', $this->service->options) ? $this->service->options['variables'] - : ['values' => ['$this->service->config->entity.public_notes' => $this->service->config->entity->public_notes, '$this->service->config->entity.terms' => $this->service->config->entity->terms, '$this->service->config->entity_footer' => $this->service->config->entity->footer], 'labels' => []]; + : ['values' => ['$entity.public_notes' => $this->service->config->entity->public_notes, '$entity.terms' => $this->service->config->entity->terms, '$entity_footer' => $this->service->config->entity->footer], 'labels' => []]; $variables = $this->service->config->pdf_variables['total_columns']; $elements = [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ - ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$this->service->config->entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], + ['element' => 'p', 'content' => strtr(str_replace(["labels","values"], ["",""], $_variables['values']['$entity.public_notes']), $_variables), 'properties' => ['data-ref' => 'total_table-public_notes', 'style' => 'text-align: left;']], ['element' => 'p', 'content' => '', 'properties' => ['style' => 'text-align: left; display: flex; flex-direction: column; page-break-inside: auto;'], 'elements' => [ - ['element' => 'span', 'content' => '$this->service->config->entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$this->service->config->entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], - ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$this->service->config->entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], + ['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']], + ['element' => 'span', 'content' => strtr(str_replace("labels", "", $_variables['values']['$entity.terms']), $_variables['labels']), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']], ]], ['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']], ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start; page-break-inside: auto;'], 'elements' => [ From ea9a3f4ca3650b78ba6a6c706f344152adadc1ab Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 8 Jan 2023 15:42:44 +1100 Subject: [PATCH 12/36] Minor cleanup --- app/Jobs/Entity/CreateEntityPdf.php | 16 ---------------- app/Services/Pdf/PdfBuilder.php | 3 +-- app/Services/Pdf/PdfService.php | 7 +++++-- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php index 60ec71508a04..2f7d59983223 100644 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ b/app/Jobs/Entity/CreateEntityPdf.php @@ -95,22 +95,6 @@ class CreateEntityPdf implements ShouldQueue } $file_path = $path.$this->entity->numberFormatter().'.pdf'; - - // $state = [ - // 'template' => $template->elements([ - // 'client' => $this->client, - // 'entity' => $this->entity, - // 'pdf_variables' => (array) $this->company->settings->pdf_variables, - // '$product' => $design->design->product, - // 'variables' => $variables, - // ]), - // 'variables' => $variables, - // 'options' => [ - // 'all_pages_header' => $this->entity->client->getSetting('all_pages_header'), - // 'all_pages_footer' => $this->entity->client->getSetting('all_pages_footer'), - // ], - // 'process_markdown' => $this->entity->client->company->markdown_enabled, - // ]; $pdf = (new PdfService($this->invitation))->getPdf(); diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 0179456581a8..8992d72133bf 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -81,7 +81,7 @@ class PdfBuilder * Final method to get compiled HTML. * * @param bool $final @deprecated // is it? i still see it being called elsewhere - * @return mixed + * @return string */ public function getCompiledHTML($final = false) { @@ -655,7 +655,6 @@ class PdfBuilder public function buildTableHeader(string $type): array { $this->processTaxColumns($type); - // $this->processCustomColumns($type); $elements = []; diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 7ee6c2511ee9..881757f1f21a 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -13,6 +13,9 @@ namespace App\Services\Pdf; use App\Models\Account; use App\Models\Company; +use App\Models\CreditInvitation; +use App\Models\InvoiceInvitation; +use App\Models\QuoteInvitation; use App\Services\Pdf\PdfConfiguration; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; @@ -25,7 +28,7 @@ class PdfService use PdfMaker, PageNumbering; - public $invitation; + public InvoiceInvitation | QuoteInvitation | CreditInvitation $invitation; public Company $company; @@ -76,7 +79,7 @@ class PdfService * attempts to generate a PDF from the HTML * string. * - * @return mixed + * @return mixed | Exception * */ public function getPdf() From 05192c9f872b1f8c8e345ca2ff7a044f3a6c7df3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 8 Jan 2023 16:15:04 +1100 Subject: [PATCH 13/36] Push purchase order PDf creator into unified system --- .../VendorPortal/InvitationController.php | 2 +- app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 141 ++---------------- app/Services/Pdf/PdfConfiguration.php | 79 +++------- app/Services/Pdf/PdfService.php | 9 +- 4 files changed, 41 insertions(+), 190 deletions(-) diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index b40df7a8ec24..c0414d9e6655 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -110,7 +110,7 @@ class InvitationController extends Controller $file_name = $invitation->purchase_order->numberFormatter().'.pdf'; - $file = (new CreatePurchaseOrderPdf($invitation))->rawPdf(); + $file = (new CreatePurchaseOrderPdf($invitation))->handle(); $headers = ['Content-Type' => 'application/pdf']; diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index 259b5b0c2525..b17081c80e16 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -26,6 +26,7 @@ use App\Models\RecurringInvoiceInvitation; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; use App\Services\PdfMaker\PdfMaker as PdfMakerService; +use App\Services\Pdf\PdfService; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\Ninja; @@ -49,25 +50,14 @@ use setasign\Fpdi\PdfParser\StreamReader; class CreatePurchaseOrderPdf implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, NumberFormatter, MakesInvoiceHtml, PdfMaker, MakesHash, PageNumbering; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $entity; - public $company; - - public $contact; - private $disk; public $invitation; - public $entity_string = ''; - - public $vendor; - - private string $path = ''; - - private string $file_path = ''; /** * Create a new job instance. @@ -77,15 +67,12 @@ class CreatePurchaseOrderPdf implements ShouldQueue public function __construct($invitation, $disk = null) { $this->invitation = $invitation; - $this->company = $invitation->company; $this->entity = $invitation->purchase_order; - $this->entity_string = 'purchase_order'; $this->contact = $invitation->contact; $this->vendor = $invitation->contact->vendor; - $this->vendor->load('company'); $this->disk = $disk ?? config('filesystems.default'); @@ -94,128 +81,20 @@ class CreatePurchaseOrderPdf implements ShouldQueue public function handle() { - $pdf = $this->rawPdf(); + MultiDB::setDb($this->invitation->company->db); + + $file_path = $this->vendor->purchase_order_filepath($this->invitation); + + $pdf = (new PdfService($this->invitation, 'purchase_order'))->getPdf(); if ($pdf) { - - try{ - Storage::disk($this->disk)->put($this->file_path, $pdf); - } - catch(\Exception $e) - { + try { + Storage::disk($this->disk)->put($file_path, $pdf); + } catch (\Exception $e) { throw new FilePermissionsFailure($e->getMessage()); } } - return $this->file_path; - } - - public function rawPdf() - { - - MultiDB::setDb($this->company->db); - - /* Forget the singleton*/ - App::forgetInstance('translator'); - - /* Init a new copy of the translator*/ - $t = app('translator'); - /* Set the locale*/ - App::setLocale($this->company->locale()); - - /* Set customized translations _NOW_ */ - $t->replace(Ninja::transformTranslations($this->company->settings)); - - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->generate($this->invitation, true); - } - - $entity_design_id = ''; - - $this->path = $this->vendor->purchase_order_filepath($this->invitation); - $entity_design_id = 'purchase_order_design_id'; - - $this->file_path = $this->path.$this->entity->numberFormatter().'.pdf'; - - $entity_design_id = $this->entity->design_id ? $this->entity->design_id : $this->decodePrimaryKey('Wpmbk5ezJn'); - - $design = Design::find($entity_design_id); - - /* Catch all in case migration doesn't pass back a valid design */ - if(!$design) - $design = Design::find(2); - - $html = new VendorHtmlEngine($this->invitation); - - if ($design->is_custom) { - $options = [ - 'custom_partials' => json_decode(json_encode($design->design), true) - ]; - $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); - } else { - $template = new PdfMakerDesign(strtolower($design->name)); - } - - $variables = $html->generateLabelsAndValues(); - - $state = [ - 'template' => $template->elements([ - 'client' => null, - 'vendor' => $this->vendor, - 'entity' => $this->entity, - 'pdf_variables' => (array) $this->company->settings->pdf_variables, - '$product' => $design->design->product, - 'variables' => $variables, - ]), - 'variables' => $variables, - 'options' => [ - 'all_pages_header' => $this->entity->company->getSetting('all_pages_header'), - 'all_pages_footer' => $this->entity->company->getSetting('all_pages_footer'), - ], - 'process_markdown' => $this->entity->company->markdown_enabled, - ]; - - $maker = new PdfMakerService($state); - - $maker - ->design($template) - ->build(); - - $pdf = null; - - try { - - if(config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja'){ - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if($numbered_pdf) - $pdf = $numbered_pdf; - - } - else { - - $pdf = $this->makePdf(null, null, $maker->getCompiledHTML(true)); - - $numbered_pdf = $this->pageNumbering($pdf, $this->company); - - if($numbered_pdf) - $pdf = $numbered_pdf; - - } - - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); - } - - if (config('ninja.log_pdf_html')) { - info($maker->getCompiledHTML()); - } - - $maker = null; - $state = null; - return $pdf; } diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 9b908a487d6a..00700991e09b 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -55,8 +55,6 @@ class PdfConfiguration public string $entity_string; - public $service; - public array $pdf_variables; public Currency $currency; @@ -67,20 +65,14 @@ class PdfConfiguration * @var App\Models\Client | App\Models\Vendor * */ - public $currency_entity; + public Client | Vendor $currency_entity; - public function __construct(PdfService $service) - { - - $this->service = $service; - - } + public function __construct(public PdfService $service){} public function init(): self { $this->setEntityType() - ->setEntityProperties() ->setPdfVariables() ->setDesign() ->setCurrency() @@ -140,88 +132,63 @@ class PdfConfiguration private function setEntityType() { + $entity_design_id = ''; + if ($this->service->invitation instanceof InvoiceInvitation) { $this->entity = $this->service->invitation->invoice; $this->entity_string = 'invoice'; - } elseif ($this->service->invitation instanceof QuoteInvitation) { - $this->entity = $this->service->invitation->quote; - $this->entity_string = 'quote'; - } elseif ($this->service->invitation instanceof CreditInvitation) { - $this->entity = $this->service->invitation->credit; - $this->entity_string = 'credit'; - } elseif ($this->service->invitation instanceof RecurringInvoiceInvitation) { - $this->entity = $this->service->invitation->recurring_invoice; - $this->entity_string = 'recurring_invoice'; - } elseif ($this->service->invitation instanceof PurchaseOrderInvitation) { - $this->entity = $this->service->invitation->purchase_order; - $this->entity_string = 'purchase_order'; - } else { - throw new \Exception('Unable to resolve entity', 500); - } - - return $this; - - } - - private function setEntityProperties() - { - - $entity_design_id = ''; - - if ($this->entity instanceof Invoice) { - $this->client = $this->entity->client; $this->contact = $this->service->invitation->contact; $this->path = $this->client->invoice_filepath($this->service->invitation); $this->entity_design_id = 'invoice_design_id'; $this->settings = $this->client->getMergedSettings(); $this->settings_object = $this->client; - - } elseif ($this->entity instanceof Quote) { - + } elseif ($this->service->invitation instanceof QuoteInvitation) { + $this->entity = $this->service->invitation->quote; + $this->entity_string = 'quote'; $this->client = $this->entity->client; $this->contact = $this->service->invitation->contact; $this->path = $this->client->quote_filepath($this->service->invitation); $this->entity_design_id = 'quote_design_id'; $this->settings = $this->client->getMergedSettings(); $this->settings_object = $this->client; - - } elseif ($this->entity instanceof Credit) { - + } elseif ($this->service->invitation instanceof CreditInvitation) { + $this->entity = $this->service->invitation->credit; + $this->entity_string = 'credit'; $this->client = $this->entity->client; $this->contact = $this->service->invitation->contact; $this->path = $this->client->credit_filepath($this->service->invitation); $this->entity_design_id = 'credit_design_id'; $this->settings = $this->client->getMergedSettings(); $this->settings_object = $this->client; - - } elseif ($this->entity instanceof RecurringInvoice) { - + } elseif ($this->service->invitation instanceof RecurringInvoiceInvitation) { + $this->entity = $this->service->invitation->recurring_invoice; + $this->entity_string = 'recurring_invoice'; $this->client = $this->entity->client; $this->contact = $this->service->invitation->contact; $this->path = $this->client->recurring_invoice_filepath($this->service->invitation); $this->entity_design_id = 'invoice_design_id'; $this->settings = $this->client->getMergedSettings(); $this->settings_object = $this->client; - - } elseif ($this->entity instanceof PurchaseOrder) { - + } elseif ($this->service->invitation instanceof PurchaseOrderInvitation) { + $this->entity = $this->service->invitation->purchase_order; + $this->entity_string = 'purchase_order'; $this->vendor = $this->entity->vendor; $this->vendor_contact = $this->service->invitation->contact; $this->path = $this->vendor->purchase_order_filepath($this->service->invitation); $this->entity_design_id = 'invoice_design_id'; $this->entity_design_id = 'purchase_order_design_id'; - $this->settings = $this->vendor->getMergedSettings(); - $this->settings_object = $this->client; - - } - else - throw new \Exception('Unable to resolve entity properties type', 500); + $this->settings = $this->vendor->company->settings; + $this->settings_object = $this->vendor; + $this->client = null; + } else { + throw new \Exception('Unable to resolve entity', 500); + } $this->path = $this->path.$this->entity->numberFormatter().'.pdf'; return $this; - + } private function setDesign() diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 881757f1f21a..943a6a9dcdb0 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -15,20 +15,23 @@ use App\Models\Account; use App\Models\Company; use App\Models\CreditInvitation; use App\Models\InvoiceInvitation; +use App\Models\PurchaseOrderInvitation; use App\Models\QuoteInvitation; +use App\Models\RecurringInvoiceInvitation; use App\Services\Pdf\PdfConfiguration; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\PhantomJS\Phantom; use App\Utils\Traits\Pdf\PageNumbering; use App\Utils\Traits\Pdf\PdfMaker; +use App\Utils\VendorHtmlEngine; class PdfService { use PdfMaker, PageNumbering; - public InvoiceInvitation | QuoteInvitation | CreditInvitation $invitation; + public InvoiceInvitation | QuoteInvitation | CreditInvitation | RecurringInvoiceInvitation | PurchaseOrderInvitation $invitation; public Company $company; @@ -62,7 +65,9 @@ class PdfService $this->config = (new PdfConfiguration($this))->init(); - $this->html_variables = (new HtmlEngine($invitation))->generateLabelsAndValues(); + $this->html_variables = $this->config->client ? + (new HtmlEngine($invitation))->generateLabelsAndValues() : + (new VendorHtmlEngine($invitation))->generateLabelsAndValues(); $this->designer = (new PdfDesigner($this))->build(); From ecdffcae14db3c19e2c3196d1859b3722742497c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 8 Jan 2023 20:44:13 +1100 Subject: [PATCH 14/36] Working on purchase order PDFs --- app/Jobs/Vendor/CreatePurchaseOrderPdf.php | 2 -- app/Services/Pdf/PdfService.php | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php index b17081c80e16..dfe9836e99f6 100644 --- a/app/Jobs/Vendor/CreatePurchaseOrderPdf.php +++ b/app/Jobs/Vendor/CreatePurchaseOrderPdf.php @@ -70,8 +70,6 @@ class CreatePurchaseOrderPdf implements ShouldQueue $this->entity = $invitation->purchase_order; - $this->contact = $invitation->contact; - $this->vendor = $invitation->contact->vendor; $this->disk = $disk ?? config('filesystems.default'); diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 943a6a9dcdb0..7bb06d6bad35 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -19,6 +19,7 @@ use App\Models\PurchaseOrderInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; use App\Services\Pdf\PdfConfiguration; +use App\Services\Pdf\PdfDesigner; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\PhantomJS\Phantom; From 66571b1cbd2961101560cd3f832c7e6ca90fa915 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 11 Jan 2023 23:46:45 +1100 Subject: [PATCH 15/36] Fixes for displaying purchase order table in pdf --- app/Services/Pdf/PdfBuilder.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index 8992d72133bf..d54ab6381a49 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -30,6 +30,7 @@ class PdfBuilder private CommonMarkConverter $commonmark; + private float $payment_amount_total = 0; /** * an array of sections to be injected into the template * @@ -266,6 +267,7 @@ class PdfBuilder $tbody[] = $element; + $this->payment_amount_total += $payment->pivot->amount; } } @@ -295,7 +297,8 @@ class PdfBuilder $payment = $this->service->options['payments']->first(); return [ - ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->service->options['payments']->sum('amount'), $this->service->config->client))], + // ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->service->options['payments']->sum('amount'), $this->service->config->client))], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.amount_paid'), Number::formatMoney($this->payment_amount_total, $this->client))], ]; } @@ -338,6 +341,7 @@ class PdfBuilder { $this->genericSectionBuilder() + ->getProductAndTaskTables() ->getProductTotals(); $this->mergeSections([ @@ -1100,7 +1104,7 @@ class PdfBuilder } -/** + /** * Generates the product table * * @return array From b60f0bc5dc5ce668fe8ae26cb8d469606d6983e6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 12 Jan 2023 00:16:53 +1100 Subject: [PATCH 16/36] Minor clean up --- app/Services/Pdf/PdfBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index d54ab6381a49..c12da4e88ae6 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -691,7 +691,7 @@ class PdfBuilder } - /** + /** * This method will help us decide either we show * one "tax rate" column in the table or 3 custom tax rates. * @@ -858,7 +858,7 @@ class PdfBuilder private function buildSections() :self { - return match ($this->service->document_type) { + return match ($this->service->document_type) { PdfService::PRODUCT => $this->getProductSections(), PdfService::DELIVERY_NOTE => $this->getDeliveryNoteSections(), PdfService::STATEMENT => $this->getStatementSections(), From 10793f1d14673492e7ce5e51ede9ee2515809289 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 21 Feb 2023 18:39:07 +1100 Subject: [PATCH 17/36] Updates for custom PDF designer --- app/Services/Pdf/PdfBuilder.php | 290 +++++++++----------------- app/Services/Pdf/PdfConfiguration.php | 34 +-- app/Services/Pdf/PdfDesigner.php | 28 +-- app/Services/Pdf/PdfService.php | 54 +---- tests/Pdf/PdfGenerationTest.php | 2 +- 5 files changed, 127 insertions(+), 281 deletions(-) diff --git a/app/Services/Pdf/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php index c12da4e88ae6..3d21f4aaa2c4 100644 --- a/app/Services/Pdf/PdfBuilder.php +++ b/app/Services/Pdf/PdfBuilder.php @@ -33,7 +33,7 @@ class PdfBuilder private float $payment_amount_total = 0; /** * an array of sections to be injected into the template - * + * * @var array */ public array $sections = []; @@ -46,29 +46,26 @@ class PdfBuilder public DomDocument $document; /** - * @param PdfService $service - * @return void + * @param PdfService $service + * @return void */ public function __construct(PdfService $service) { - $this->service = $service; $this->commonmark = new CommonMarkConverter([ 'allow_unsafe_links' => false, ]); - } /** * Builds the template sections - * + * * @return self - * + * */ public function build(): self { - $this->getTemplate() ->buildSections() ->getEmptyElements() @@ -93,13 +90,12 @@ class PdfBuilder /** * Generate the template - * + * * @return self - * + * */ private function getTemplate() :self { - $document = new DOMDocument(); $document->validateOnParse = true; @@ -111,50 +107,44 @@ class PdfBuilder $this->xpath = new DOMXPath($document); return $this; - } /** * Generates product entity sections - * + * * @return self - * + * */ private function getProductSections(): self { - $this->genericSectionBuilder() ->getClientDetails() ->getProductAndTaskTables() ->getProductEntityDetails() ->getProductTotals(); - return $this; - + return $this; } private function mergeSections(array $section) :self { - $this->sections = array_merge($this->sections, $section); return $this; - } /** * Generates delivery note sections - * + * * @return self - * + * */ private function getDeliveryNoteSections(): self { - $this->genericSectionBuilder() ->getProductTotals(); - $this->mergeSections([ + $this->mergeSections([ 'client-details' => [ 'id' => 'client-details', 'elements' => $this->clientDeliveryDetails(), @@ -170,21 +160,19 @@ class PdfBuilder ]); return $this; - } /** * Generates statement sections - * + * * @return self - * + * */ private function getStatementSections(): self { - $this->genericSectionBuilder(); - $this->mergeSections( [ + $this->mergeSections([ 'statement-invoice-table' => [ 'id' => 'statement-invoice-table', 'elements' => $this->statementInvoiceTable(), @@ -212,7 +200,6 @@ class PdfBuilder ]); return $this; - } /** @@ -223,13 +210,11 @@ class PdfBuilder */ public function statementInvoiceTableTotals(): array { - $outstanding = $this->service->options['invoices']->sum('balance'); return [ ['element' => 'p', 'content' => '$outstanding_label: ' . Number::formatMoney($outstanding, $this->service->config->client)], ]; - } @@ -240,7 +225,6 @@ class PdfBuilder */ public function statementPaymentTable(): array { - if (is_null($this->service->option['payments'])) { return []; } @@ -254,9 +238,9 @@ class PdfBuilder //24-03-2022 show payments per invoice foreach ($this->service->options['invoices'] as $invoice) { foreach ($invoice->payments as $payment) { - - if($payment->is_deleted) + if ($payment->is_deleted) { continue; + } $element = ['element' => 'tr', 'elements' => []]; @@ -275,14 +259,13 @@ class PdfBuilder ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')], ['element' => 'tbody', 'elements' => $tbody], ]; - } /** * Generates the statement payments table - * + * * @return array - * + * */ public function statementPaymentTableTotals(): array { @@ -304,13 +287,12 @@ class PdfBuilder /** * Generates the statement aging table - * + * * @return array - * + * */ public function statementAgingTable(): array { - if (\array_key_exists('show_aging_table', $this->service->options) && $this->service->options['show_aging_table'] === false) { return []; } @@ -333,18 +315,17 @@ class PdfBuilder /** * Generates the purchase order sections - * + * * @return self - * + * */ private function getPurchaseOrderSections(): self { - $this->genericSectionBuilder() ->getProductAndTaskTables() ->getProductTotals(); - $this->mergeSections([ + $this->mergeSections([ 'vendor-details' => [ 'id' => 'vendor-details', 'elements' => $this->vendorDetails(), @@ -356,19 +337,17 @@ class PdfBuilder ]); return $this; - } /** * Generates the generic section which apply * across all design templates - * + * * @return self - * + * */ private function genericSectionBuilder(): self { - $this->mergeSections([ 'company-details' => [ 'id' => 'company-details', @@ -391,13 +370,12 @@ class PdfBuilder /** * Generates the invoices table for statements - * + * * @return array - * + * */ public function statementInvoiceTable(): array { - $tbody = []; foreach ($this->service->options['invoices'] as $invoice) { @@ -424,7 +402,7 @@ class PdfBuilder * * @param string $type "$product" or "$task" * @return array - * + * */ public function buildTableBody(string $type): array { @@ -516,7 +494,7 @@ class PdfBuilder $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax2-td']]; } elseif ($cell == '$product.tax_rate3') { $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => 'product_table-product.tax3-td']]; - } else if ($cell == '$product.unit_cost' || $cell == '$task.rate') { + } elseif ($cell == '$product.unit_cost' || $cell == '$task.rate') { $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['style' => 'white-space: nowrap;', 'data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; } else { $element['elements'][] = ['element' => 'td', 'content' => $row[$cell], 'properties' => ['data-ref' => "{$_type}_table-" . substr($cell, 1) . '-td']]; @@ -542,8 +520,7 @@ class PdfBuilder * @return array */ public function transformLineItems($items, $table_type = '$product') :array - { - + { $data = []; $locale_info = localeconv(); @@ -559,7 +536,7 @@ class PdfBuilder if ($table_type == '$task' && $item->type_id != 2) { // if ($item->type_id != 4 && $item->type_id != 5) { - continue; + continue; // } } @@ -654,7 +631,7 @@ class PdfBuilder * * @param string $type "product" or "task" * @return array - * + * */ public function buildTableHeader(string $type): array { @@ -688,7 +665,6 @@ class PdfBuilder } return $elements; - } /** @@ -703,7 +679,6 @@ class PdfBuilder */ public function processTaxColumns(string $type): void { - if ($type == 'product') { $type_id = 1; } @@ -746,15 +721,14 @@ class PdfBuilder array_splice($this->service->config->pdf_variables["{$type}_columns"], $key, 1, $taxes); } } - } /** - * Generates the javascript block for + * Generates the javascript block for * hiding elements which need to be hidden - * + * * @return array - * + * */ public function sharedFooterElements(): array { @@ -777,20 +751,18 @@ class PdfBuilder ['element' => 'script', 'content' => $javascript], ['element' => 'script', 'content' => $html_decode], ]]; - } /** - * Generates the totals table for + * Generates the totals table for * the product type entities - * + * * @return self - * + * */ private function getProductTotals(): self { - - $this->mergeSections([ + $this->mergeSections([ 'table-totals' => [ 'id' => 'table-totals', 'elements' => $this->getTableTotals(), @@ -798,7 +770,6 @@ class PdfBuilder ]); return $this; - } /** @@ -806,76 +777,62 @@ class PdfBuilder * Credits * Quotes * Invoices - * + * * @return self - * + * */ private function getProductEntityDetails(): self { - - if($this->service->config->entity_string == 'invoice') - { - $this->mergeSections( [ + if ($this->service->config->entity_string == 'invoice') { + $this->mergeSections([ 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->invoiceDetails(), ], ]); - } - elseif($this->service->config->entity_string == 'quote') - { - - $this->mergeSections( [ + } elseif ($this->service->config->entity_string == 'quote') { + $this->mergeSections([ 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->quoteDetails(), ], ]); - - } - elseif($this->service->config->entity_string == 'credit') - { - - $this->mergeSections( [ + } elseif ($this->service->config->entity_string == 'credit') { + $this->mergeSections([ 'entity-details' => [ 'id' => 'entity-details', 'elements' => $this->creditDetails(), ], ]); - } return $this; - } /** * Parent entry point when building sections of the design content - * + * * @return self - * + * */ private function buildSections() :self { - return match ($this->service->document_type) { PdfService::PRODUCT => $this->getProductSections(), PdfService::DELIVERY_NOTE => $this->getDeliveryNoteSections(), PdfService::STATEMENT => $this->getStatementSections(), PdfService::PURCHASE_ORDER => $this->getPurchaseOrderSections(), - }; - + }; } /** * Generates the table totals for statements - * + * * @return array - * + * */ private function statementTableTotals(): array { - return [ ['element' => 'div', 'properties' => ['style' => 'display: flex; flex-direction: column;'], 'elements' => [ ['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: block; align-items: flex-start; page-break-inside: avoid; visible !important;'], 'elements' => [ @@ -883,16 +840,15 @@ class PdfBuilder ]], ]], ]; - } /** * Performs a variable check to ensure * the variable exists - * + * * @param string $variables * @return bool - * + * */ public function entityVariableCheck(string $variable): bool { @@ -925,15 +881,14 @@ class PdfBuilder } return false; - } //First pass done, need a second pass to abstract this content completely. /** * Builds the table totals for all entities, we'll want to split this - * + * * @return array - * + * */ public function getTableTotals() :array { @@ -980,7 +935,6 @@ class PdfBuilder if (in_array('$paid_to_date', $variables)) { $variables = \array_diff($variables, ['$paid_to_date']); } - } foreach (['discount'] as $property) { @@ -1057,19 +1011,17 @@ class PdfBuilder ]]; return $elements; - } /** * Generates the product and task tables - * + * * @return self - * + * */ public function getProductAndTaskTables(): self { - - $this->mergeSections( [ + $this->mergeSections([ 'product-table' => [ 'id' => 'product-table', 'elements' => $this->productTable(), @@ -1081,19 +1033,17 @@ class PdfBuilder ]); return $this; - } /** * Generates the client details - * + * * @return self - * + * */ public function getClientDetails(): self { - - $this->mergeSections( [ + $this->mergeSections([ 'client-details' => [ 'id' => 'client-details', 'elements' => $this->clientDetails(), @@ -1101,7 +1051,6 @@ class PdfBuilder ]); return $this; - } /** @@ -1111,7 +1060,6 @@ class PdfBuilder */ public function productTable(): array { - $product_items = collect($this->service->config->entity->line_items)->filter(function ($item) { return $item->type_id == 1 || $item->type_id == 6 || $item->type_id == 5; }); @@ -1124,7 +1072,6 @@ class PdfBuilder ['element' => 'thead', 'elements' => $this->buildTableHeader('product')], ['element' => 'tbody', 'elements' => $this->buildTableBody('$product')], ]; - } /** @@ -1134,7 +1081,6 @@ class PdfBuilder */ public function taskTable(): array { - $task_items = collect($this->service->config->entity->line_items)->filter(function ($item) { return $item->type_id == 2; }); @@ -1147,19 +1093,17 @@ class PdfBuilder ['element' => 'thead', 'elements' => $this->buildTableHeader('task')], ['element' => 'tbody', 'elements' => $this->buildTableBody('$task')], ]; - } /** * Generates the statement details - * + * * @return array - * + * */ public function statementDetails(): array { - $s_date = $this->translateDate(now(), $this->service->config->client->date_format(), $this->service->config->client->locale()); return [ @@ -1176,33 +1120,29 @@ class PdfBuilder ['element' => 'th', 'properties' => [], 'content' => Number::formatMoney($this->service->options['invoices']->sum('balance'), $this->service->config->client)], ]], ]; - } /** * Generates the invoice details - * + * * @return array - * + * */ public function invoiceDetails(): array { - - $variables = $this->service->config->pdf_variables['invoice_details']; + $variables = $this->service->config->pdf_variables['invoice_details']; return $this->genericDetailsBuilder($variables); - } /** * Generates the quote details - * + * * @return array - * + * */ public function quoteDetails(): array { - $variables = $this->service->config->pdf_variables['quote_details']; if ($this->service->config->entity->partial > 0) { @@ -1210,48 +1150,42 @@ class PdfBuilder } return $this->genericDetailsBuilder($variables); - } /** * Generates the credit note details - * + * * @return array - * + * */ public function creditDetails(): array { - $variables = $this->service->config->pdf_variables['credit_details']; return $this->genericDetailsBuilder($variables); - } /** * Generates the purchase order details - * + * * @return array */ public function purchaseOrderDetails(): array { - $variables = $this->service->config->pdf_variables['purchase_order_details']; return $this->genericDetailsBuilder($variables); - } /** * Generates the deliveyr note details - * + * * @return array - * + * */ public function deliveryNoteDetails(): array { - $variables = $this->service->config->pdf_variables['invoice_details']; $variables = array_filter($variables, function ($m) { @@ -1259,19 +1193,17 @@ class PdfBuilder }); return $this->genericDetailsBuilder($variables); - } /** * Generates the custom values for the * entity. - * + * * @param array * @return array */ public function genericDetailsBuilder(array $variables): array { - $elements = []; @@ -1295,24 +1227,23 @@ class PdfBuilder } return $elements; - } /** * Generates the client delivery * details array - * + * * @return array - * + * */ public function clientDeliveryDetails(): array { - $elements = []; - if(!$this->service->config->client) + if (!$this->service->config->client) { return $elements; + } $elements = [ ['element' => 'p', 'content' => ctrans('texts.delivery_note'), 'properties' => ['data-ref' => 'delivery_note-label', 'style' => 'font-weight: bold; text-transform: uppercase']], @@ -1327,26 +1258,25 @@ class PdfBuilder ['element' => 'p', 'content' => optional($this->service->config->client->shipping_country)->name, 'show_empty' => false], ]; - if (!is_null($this->service->config->contact)) { - $elements[] = ['element' => 'p', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; - } + if (!is_null($this->service->config->contact)) { + $elements[] = ['element' => 'p', 'content' => $this->service->config->contact->email, 'show_empty' => false, 'properties' => ['data-ref' => 'delivery_note-contact.email']]; + } return $elements; - } /** * Generates the client details section - * + * * @return array */ public function clientDetails(): array { - $elements = []; - if(!$this->service->config->client) + if (!$this->service->config->client) { return $elements; + } $variables = $this->service->config->pdf_variables['client_details']; @@ -1355,12 +1285,11 @@ class PdfBuilder } return $elements; - } /** * Generates the delivery note table - * + * * @return array */ public function deliveryNoteTable(): array @@ -1396,20 +1325,18 @@ class PdfBuilder ['element' => 'thead', 'elements' => $thead], ['element' => 'tbody', 'elements' => $this->buildTableBody(PdfService::DELIVERY_NOTE)], ]; - } /** * Passes an array of items by reference * and performs a nl2br - * + * * @param array * @return void - * + * */ public function processNewLines(array &$items): void { - foreach ($items as $key => $item) { foreach ($item as $variable => $value) { $item[$variable] = str_replace("\n", '
', $value); @@ -1417,18 +1344,16 @@ class PdfBuilder $items[$key] = $item; } - } /** * Generates an arary of the company details - * + * * @return array - * + * */ public function companyDetails(): array { - $variables = $this->service->config->pdf_variables['company_details']; $elements = []; @@ -1438,19 +1363,17 @@ class PdfBuilder } return $elements; - } /** * * Generates an array of the company address - * + * * @return array - * + * */ public function companyAddress(): array { - $variables = $this->service->config->pdf_variables['company_address']; $elements = []; @@ -1460,19 +1383,17 @@ class PdfBuilder } return $elements; - } /** * * Generates an array of vendor details - * + * * @return array - * + * */ public function vendorDetails(): array { - $elements = []; $variables = $this->service->config->pdf_variables['vendor_details']; @@ -1482,7 +1403,6 @@ class PdfBuilder } return $elements; - } @@ -1493,14 +1413,11 @@ class PdfBuilder public function getSectionNode(string $selector) { - return $this->document->getElementById($selector); - } public function updateElementProperties() :self { - foreach ($this->sections as $element) { if (isset($element['tag'])) { $node = $this->document->getElementsByTagName($element['tag'])->item(0); @@ -1522,7 +1439,6 @@ class PdfBuilder } return $this; - } public function updateElementProperty($element, string $attribute, ?string $value) @@ -1542,12 +1458,10 @@ class PdfBuilder } return $element; - } public function createElementContent($element, $children) :self { - foreach ($children as $child) { $contains_html = false; @@ -1596,12 +1510,10 @@ class PdfBuilder } return $this; - } public function updateVariables() { - $html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']); $html = strtr($html, $this->service->html_variables['values']); @@ -1611,12 +1523,10 @@ class PdfBuilder $this->document->saveHTML(); return $this; - } public function updateVariable(string $element, string $variable, string $value) { - $element = $this->document->getElementById($element); $original = $element->nodeValue; @@ -1630,12 +1540,10 @@ class PdfBuilder ); return $element; - } public function getEmptyElements() :self { - foreach ($this->sections as $element) { if (isset($element['elements'])) { $this->getEmptyChildrens($element['elements'], $this->service->html_variables); @@ -1643,12 +1551,10 @@ class PdfBuilder } return $this; - } public function getEmptyChildrens(array $children) { - foreach ($children as $key => $child) { if (isset($child['content']) && isset($child['show_empty']) && $child['show_empty'] === false) { $value = strtr($child['content'], $this->service->html_variables['values']); @@ -1663,7 +1569,5 @@ class PdfBuilder } return $this; - } - -} \ No newline at end of file +} diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 00700991e09b..728952e5c948 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -14,21 +14,15 @@ namespace App\Services\Pdf; use App\DataMapper\CompanySettings; use App\Models\Client; use App\Models\ClientContact; -use App\Models\Credit; use App\Models\CreditInvitation; use App\Models\Currency; use App\Models\Design; -use App\Models\Invoice; use App\Models\InvoiceInvitation; -use App\Models\PurchaseOrder; use App\Models\PurchaseOrderInvitation; -use App\Models\Quote; use App\Models\QuoteInvitation; -use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; use App\Models\Vendor; use App\Models\VendorContact; -use App\Services\Pdf\PdfService; use App\Utils\Ninja; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\App; @@ -59,19 +53,23 @@ class PdfConfiguration public Currency $currency; + public ?string $path; + + public int $entity_design_id; /** - * The parent object of the currency - * + * The parent object of the currency + * * @var App\Models\Client | App\Models\Vendor - * + * */ public Client | Vendor $currency_entity; - public function __construct(public PdfService $service){} + public function __construct(public PdfService $service) + { + } public function init(): self { - $this->setEntityType() ->setPdfVariables() ->setDesign() @@ -79,12 +77,10 @@ class PdfConfiguration ->setLocale(); return $this; - } private function setLocale(): self { - App::forgetInstance('translator'); $t = app('translator'); @@ -94,23 +90,19 @@ class PdfConfiguration $t->replace(Ninja::transformTranslations($this->settings)); return $this; - } private function setCurrency(): self { - $this->currency = $this->client ? $this->client->currency() : $this->vendor->currency(); $this->currency_entity = $this->client ? $this->client : $this->vendor; return $this; - } private function setPdfVariables() :self { - $default = (array) CompanySettings::getEntityVariableDefaults(); $variables = (array)$this->service->company->settings->pdf_variables; @@ -126,12 +118,10 @@ class PdfConfiguration $this->pdf_variables = $variables; return $this; - } private function setEntityType() { - $entity_design_id = ''; if ($this->service->invitation instanceof InvoiceInvitation) { @@ -188,18 +178,14 @@ class PdfConfiguration $this->path = $this->path.$this->entity->numberFormatter().'.pdf'; return $this; - } private function setDesign() { - $design_id = $this->entity->design_id ? : $this->decodePrimaryKey($this->settings_object->getSetting($this->entity_design_id)); $this->design = Design::find($design_id ?: 2); return $this; - } - -} \ No newline at end of file +} diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php index 3b5bc6321a35..5db92b32a390 100644 --- a/app/Services/Pdf/PdfDesigner.php +++ b/app/Services/Pdf/PdfDesigner.php @@ -13,7 +13,6 @@ namespace App\Services\Pdf; class PdfDesigner { - const BOLD = 'bold'; const BUSINESS = 'business'; const CLEAN = 'clean'; @@ -30,29 +29,19 @@ class PdfDesigner const STATEMENT = 'statement'; const PURCHASE_ORDER = 'purchase_order'; - - public PdfService $service; - public string $template; - public function __construct(PdfService $service) + public function __construct(public PdfService $service) { - $this->service = $service; } public function build() :self { - /*If the design is custom*/ - if ($this->service->config->design->is_custom) - { - + if ($this->service->config->design->is_custom) { $this->template = $this->composeFromPartials(json_decode(json_encode($this->service->config->design->design), true)); - - } - else - { - $this->template = file_get_contents(config('ninja.designs.base_path') . strtolower($this->service->config->design->name) . '.html'); + } else { + $this->template = file_get_contents(config('ninja.designs.base_path') . strtolower($this->service->config->design->name) . '.html'); } return $this; @@ -63,12 +52,12 @@ class PdfDesigner */ /** - * Returns the custom HTML design as + * Returns the custom HTML design as * a string - * + * * @param array * @return string - * + * */ private function composeFromPartials(array $partials) :string { @@ -81,5 +70,4 @@ class PdfDesigner return $html; } - -} \ No newline at end of file +} diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php index 7bb06d6bad35..92140982d6f0 100644 --- a/app/Services/Pdf/PdfService.php +++ b/app/Services/Pdf/PdfService.php @@ -18,8 +18,6 @@ use App\Models\InvoiceInvitation; use App\Models\PurchaseOrderInvitation; use App\Models\QuoteInvitation; use App\Models\RecurringInvoiceInvitation; -use App\Services\Pdf\PdfConfiguration; -use App\Services\Pdf\PdfDesigner; use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HtmlEngine; use App\Utils\PhantomJS\Phantom; @@ -29,7 +27,6 @@ use App\Utils\VendorHtmlEngine; class PdfService { - use PdfMaker, PageNumbering; public InvoiceInvitation | QuoteInvitation | CreditInvitation | RecurringInvoiceInvitation | PurchaseOrderInvitation $invitation; @@ -57,7 +54,6 @@ class PdfService public function __construct($invitation, $document_type = 'product', $options = []) { - $this->invitation = $invitation; $this->company = $invitation->company; @@ -66,7 +62,7 @@ class PdfService $this->config = (new PdfConfiguration($this))->init(); - $this->html_variables = $this->config->client ? + $this->html_variables = $this->config->client ? (new HtmlEngine($invitation))->generateLabelsAndValues() : (new VendorHtmlEngine($invitation))->generateLabelsAndValues(); @@ -77,83 +73,55 @@ class PdfService $this->options = $options; $this->builder = (new PdfBuilder($this))->build(); - } /** * Resolves the PDF generation type and * attempts to generate a PDF from the HTML * string. - * + * * @return mixed | Exception - * + * */ public function getPdf() { - try { - - if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') - { - + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { $pdf = (new Phantom)->convertHtmlToPdf($this->getHtml()); - - } - elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') - { - + } elseif (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { $pdf = (new NinjaPdf())->build($this->getHtml()); - - } - else - { - + } else { $pdf = $this->makePdf(null, null, $this->getHtml()); - } $numbered_pdf = $this->pageNumbering($pdf, $this->company); - if ($numbered_pdf) - { - + if ($numbered_pdf) { $pdf = $numbered_pdf; - } - - } catch (\Exception $e) { - nlog(print_r($e->getMessage(), 1)); throw new \Exception($e->getMessage(), $e->getCode()); - } return $pdf; - } /** * Renders the dom document to HTML - * + * * @return string - * + * */ public function getHtml(): string { - $html = $this->builder->getCompiledHTML(); - if (config('ninja.log_pdf_html')) - { - + if (config('ninja.log_pdf_html')) { info($html); - } return $html; - } - -} \ No newline at end of file +} diff --git a/tests/Pdf/PdfGenerationTest.php b/tests/Pdf/PdfGenerationTest.php index 66d8e77cd5c2..22fcacf01673 100644 --- a/tests/Pdf/PdfGenerationTest.php +++ b/tests/Pdf/PdfGenerationTest.php @@ -17,7 +17,7 @@ use Tests\TestCase; /** * @test - //@covers App\DataMapper\BaseSettings + * @covers App\DataMapper\BaseSettings */ class PdfGenerationTest extends TestCase { From fe4d08a5229c97f09c71d7bf8d53996b06f2be97 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 23 Feb 2023 20:18:31 +1100 Subject: [PATCH 18/36] Minor cleanup --- app/Jobs/Entity/CreateEntityPdf.php | 22 -------------------- app/Mail/Engine/PurchaseOrderEmailEngine.php | 2 +- app/Services/Pdf/PdfConfiguration.php | 2 +- public/vendor/livewire/livewire.js | 4 ++-- public/vendor/livewire/livewire.js.map | 2 +- public/vendor/livewire/manifest.json | 2 +- 6 files changed, 6 insertions(+), 28 deletions(-) diff --git a/app/Jobs/Entity/CreateEntityPdf.php b/app/Jobs/Entity/CreateEntityPdf.php index 2f7d59983223..a3d25b754887 100644 --- a/app/Jobs/Entity/CreateEntityPdf.php +++ b/app/Jobs/Entity/CreateEntityPdf.php @@ -14,39 +14,17 @@ namespace App\Jobs\Entity; use App\Exceptions\FilePermissionsFailure; use App\Libraries\MultiDB; -use App\Models\Account; -use App\Models\Credit; use App\Models\CreditInvitation; -use App\Models\Design; -use App\Models\Invoice; use App\Models\InvoiceInvitation; -use App\Models\Quote; use App\Models\QuoteInvitation; -use App\Models\RecurringInvoice; use App\Models\RecurringInvoiceInvitation; use App\Services\Pdf\PdfService; -use App\Services\PdfMaker\Design as PdfDesignModel; -use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker as PdfMakerService; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\HtmlEngine; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Utils\Traits\NumberFormatter; -use App\Utils\Traits\Pdf\PageNumbering; -use App\Utils\Traits\Pdf\PDF; -use App\Utils\Traits\Pdf\PdfMaker; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Storage; -use setasign\Fpdi\PdfParser\StreamReader; class CreateEntityPdf implements ShouldQueue { diff --git a/app/Mail/Engine/PurchaseOrderEmailEngine.php b/app/Mail/Engine/PurchaseOrderEmailEngine.php index f11e046b68e2..bd4623cd8a4e 100644 --- a/app/Mail/Engine/PurchaseOrderEmailEngine.php +++ b/app/Mail/Engine/PurchaseOrderEmailEngine.php @@ -132,7 +132,7 @@ class PurchaseOrderEmailEngine extends BaseEmailEngine // $this->setAttachments([$this->purchase_order->pdf_file_path($this->invitation)]); // } - $pdf = (new CreatePurchaseOrderPdf($this->invitation))->rawPdf(); + $pdf = (new CreatePurchaseOrderPdf($this->invitation))->handle(); $this->setAttachments([['file' => base64_encode($pdf), 'name' => $this->purchase_order->numberFormatter().'.pdf']]); diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php index 728952e5c948..3f58f6611c94 100644 --- a/app/Services/Pdf/PdfConfiguration.php +++ b/app/Services/Pdf/PdfConfiguration.php @@ -55,7 +55,7 @@ class PdfConfiguration public ?string $path; - public int $entity_design_id; + public string $entity_design_id; /** * The parent object of the currency * diff --git a/public/vendor/livewire/livewire.js b/public/vendor/livewire/livewire.js index 4d9ef6eb70b8..35fcae6f0cf9 100644 --- a/public/vendor/livewire/livewire.js +++ b/public/vendor/livewire/livewire.js @@ -1,4 +1,4 @@ -!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):(global="undefined"!=typeof globalThis?globalThis:global||self).Livewire=factory()}(this,(function(){"use strict";function ownKeys$1(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);enumerableOnly&&(symbols=symbols.filter((function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable}))),keys.push.apply(keys,symbols)}return keys}function _objectSpread2(target){for(var i=1;iarr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i0&&void 0!==arguments[0]?arguments[0]:"right";return this.modifiers.includes("up")?"up":this.modifiers.includes("down")?"down":this.modifiers.includes("left")?"left":this.modifiers.includes("right")?"right":fallback}}]),Directive}();function walk(root,callback){if(!1!==callback(root))for(var node=root.firstElementChild;node;)walk(node,callback),node=node.nextElementSibling}function dispatch(eventName){var event=document.createEvent("Events");return event.initEvent(eventName,!0,!0),document.dispatchEvent(event),event}function getCsrfToken(){var _window$livewire_toke,tokenTag=document.head.querySelector('meta[name="csrf-token"]');return tokenTag?tokenTag.content:null!==(_window$livewire_toke=window.livewire_token)&&void 0!==_window$livewire_toke?_window$livewire_toke:void 0}function kebabCase(subject){return subject.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()} +!function(global,factory){"object"==typeof exports&&"undefined"!=typeof module?module.exports=factory():"function"==typeof define&&define.amd?define(factory):(global="undefined"!=typeof globalThis?globalThis:global||self).Livewire=factory()}(this,(function(){"use strict";function _iterableToArrayLimit(arr,i){var _i=null==arr?null:"undefined"!=typeof Symbol&&arr[Symbol.iterator]||arr["@@iterator"];if(null!=_i){var _s,_e,_x,_r,_arr=[],_n=!0,_d=!1;try{if(_x=(_i=_i.call(arr)).next,0===i){if(Object(_i)!==_i)return;_n=!1}else for(;!(_n=(_s=_x.call(_i)).done)&&(_arr.push(_s.value),_arr.length!==i);_n=!0);}catch(err){_d=!0,_e=err}finally{try{if(!_n&&null!=_i.return&&(_r=_i.return(),Object(_r)!==_r))return}finally{if(_d)throw _e}}return _arr}}function ownKeys$1(object,enumerableOnly){var keys=Object.keys(object);if(Object.getOwnPropertySymbols){var symbols=Object.getOwnPropertySymbols(object);enumerableOnly&&(symbols=symbols.filter((function(sym){return Object.getOwnPropertyDescriptor(object,sym).enumerable}))),keys.push.apply(keys,symbols)}return keys}function _objectSpread2(target){for(var i=1;iarr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i0&&void 0!==arguments[0]?arguments[0]:"right";return this.modifiers.includes("up")?"up":this.modifiers.includes("down")?"down":this.modifiers.includes("left")?"left":this.modifiers.includes("right")?"right":fallback}}]),Directive}();function walk(root,callback){if(!1!==callback(root))for(var node=root.firstElementChild;node;)walk(node,callback),node=node.nextElementSibling}function dispatch(eventName){var event=document.createEvent("Events");return event.initEvent(eventName,!0,!0),document.dispatchEvent(event),event}function getCsrfToken(){var _window$livewire_toke,tokenTag=document.head.querySelector('meta[name="csrf-token"]');return tokenTag?tokenTag.content:null!==(_window$livewire_toke=window.livewire_token)&&void 0!==_window$livewire_toke?_window$livewire_toke:void 0}function kebabCase(subject){return subject.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()} /*! * isobject * @@ -10,5 +10,5 @@ * * Copyright (c) 2014-2018, Jon Schlinkert. * Released under the MIT License. - */function join(segs,joinChar,options){return"function"==typeof options.join?options.join(segs):segs[0]+joinChar+segs[1]}function split$1(path,splitChar,options){return"function"==typeof options.split?options.split(path):path.split(splitChar)}function isValid(key,target,options){return"function"!=typeof options.isValid||options.isValid(key,target)}function isValidObject(val){return isobject(val)||Array.isArray(val)||"function"==typeof val}var _default$6=function(){function _default(el){var skipWatcher=arguments.length>1&&void 0!==arguments[1]&&arguments[1];_classCallCheck(this,_default),this.el=el,this.skipWatcher=skipWatcher,this.resolveCallback=function(){},this.rejectCallback=function(){},this.signature=(Math.random()+1).toString(36).substring(8)}return _createClass(_default,[{key:"toId",value:function(){return btoa(encodeURIComponent(this.el.outerHTML))}},{key:"onResolve",value:function(callback){this.resolveCallback=callback}},{key:"onReject",value:function(callback){this.rejectCallback=callback}},{key:"resolve",value:function(thing){this.resolveCallback(thing)}},{key:"reject",value:function(thing){this.rejectCallback(thing)}}]),_default}(),_default$5=function(_Action){_inherits(_default,_Action);var _super=_createSuper(_default);function _default(event,params,el){var _this;return _classCallCheck(this,_default),(_this=_super.call(this,el)).type="fireEvent",_this.payload={id:_this.signature,event:event,params:params},_this}return _createClass(_default,[{key:"toId",value:function(){return btoa(encodeURIComponent(this.type,this.payload.event,JSON.stringify(this.payload.params)))}}]),_default}(_default$6),MessageBus=function(){function MessageBus(){_classCallCheck(this,MessageBus),this.listeners={}}return _createClass(MessageBus,[{key:"register",value:function(name,callback){this.listeners[name]||(this.listeners[name]=[]),this.listeners[name].push(callback)}},{key:"call",value:function(name){for(var _len=arguments.length,params=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)params[_key-1]=arguments[_key];(this.listeners[name]||[]).forEach((function(callback){callback.apply(void 0,params)}))}},{key:"has",value:function(name){return Object.keys(this.listeners).includes(name)}}]),MessageBus}(),HookManager={availableHooks:["component.initialized","element.initialized","element.updating","element.updated","element.removed","message.sent","message.failed","message.received","message.processed","interceptWireModelSetValue","interceptWireModelAttachListener","beforeReplaceState","beforePushState"],bus:new MessageBus,register:function(name,callback){if(!this.availableHooks.includes(name))throw"Livewire: Referencing unknown hook: [".concat(name,"]");this.bus.register(name,callback)},call:function(name){for(var _this$bus,_len=arguments.length,params=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)params[_key-1]=arguments[_key];(_this$bus=this.bus).call.apply(_this$bus,[name].concat(params))}},DirectiveManager={directives:new MessageBus,register:function(name,callback){if(this.has(name))throw"Livewire: Directive already registered: [".concat(name,"]");this.directives.register(name,callback)},call:function(name,el,directive,component){this.directives.call(name,el,directive,component)},has:function(name){return this.directives.has(name)}},store$2={componentsById:{},listeners:new MessageBus,initialRenderIsFinished:!1,livewireIsInBackground:!1,livewireIsOffline:!1,sessionHasExpired:!1,sessionHasExpiredCallback:void 0,directives:DirectiveManager,hooks:HookManager,onErrorCallback:function(){},components:function(){var _this=this;return Object.keys(this.componentsById).map((function(key){return _this.componentsById[key]}))},addComponent:function(component){return this.componentsById[component.id]=component},findComponent:function(id){return this.componentsById[id]},getComponentsByName:function(name){return this.components().filter((function(component){return component.name===name}))},hasComponent:function(id){return!!this.componentsById[id]},tearDownComponents:function(){var _this2=this;this.components().forEach((function(component){_this2.removeComponent(component)}))},on:function(event,callback){this.listeners.register(event,callback)},emit:function(event){for(var _this$listeners,_len=arguments.length,params=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)params[_key-1]=arguments[_key];(_this$listeners=this.listeners).call.apply(_this$listeners,[event].concat(params)),this.componentsListeningForEvent(event).forEach((function(component){return component.addAction(new _default$5(event,params))}))},emitUp:function(el,event){for(var _len2=arguments.length,params=new Array(_len2>2?_len2-2:0),_key2=2;_key2<_len2;_key2++)params[_key2-2]=arguments[_key2];this.componentsListeningForEventThatAreTreeAncestors(el,event).forEach((function(component){return component.addAction(new _default$5(event,params))}))},emitSelf:function(componentId,event){var component=this.findComponent(componentId);if(component.listeners.includes(event)){for(var _len3=arguments.length,params=new Array(_len3>2?_len3-2:0),_key3=2;_key3<_len3;_key3++)params[_key3-2]=arguments[_key3];component.addAction(new _default$5(event,params))}},emitTo:function(componentName,event){for(var _len4=arguments.length,params=new Array(_len4>2?_len4-2:0),_key4=2;_key4<_len4;_key4++)params[_key4-2]=arguments[_key4];var components=this.getComponentsByName(componentName);components.forEach((function(component){component.listeners.includes(event)&&component.addAction(new _default$5(event,params))}))},componentsListeningForEventThatAreTreeAncestors:function(el,event){for(var parentIds=[],parent=el.parentElement.closest("[wire\\:id]");parent;)parentIds.push(parent.getAttribute("wire:id")),parent=parent.parentElement.closest("[wire\\:id]");return this.components().filter((function(component){return component.listeners.includes(event)&&parentIds.includes(component.id)}))},componentsListeningForEvent:function(event){return this.components().filter((function(component){return component.listeners.includes(event)}))},registerDirective:function(name,callback){this.directives.register(name,callback)},registerHook:function(name,callback){this.hooks.register(name,callback)},callHook:function(name){for(var _this$hooks,_len5=arguments.length,params=new Array(_len5>1?_len5-1:0),_key5=1;_key5<_len5;_key5++)params[_key5-1]=arguments[_key5];(_this$hooks=this.hooks).call.apply(_this$hooks,[name].concat(params))},changeComponentId:function(component,newId){var oldId=component.id;component.id=newId,component.fingerprint.id=newId,this.componentsById[newId]=component,delete this.componentsById[oldId],this.components().forEach((function(component){var children=component.serverMemo.children||{};Object.entries(children).forEach((function(_ref){var _ref2=_slicedToArray(_ref,2),key=_ref2[0],_ref2$=_ref2[1],id=_ref2$.id;_ref2$.tagName,id===oldId&&(children[key].id=newId)}))}))},removeComponent:function(component){component.tearDown(),delete this.componentsById[component.id]},onError:function(callback){this.onErrorCallback=callback},getClosestParentId:function(childId,subsetOfParentIds){var _this3=this,distancesByParentId={};subsetOfParentIds.forEach((function(parentId){var distance=_this3.getDistanceToChild(parentId,childId);distance&&(distancesByParentId[parentId]=distance)}));var closestParentId,smallestDistance=Math.min.apply(Math,_toConsumableArray(Object.values(distancesByParentId)));return Object.entries(distancesByParentId).forEach((function(_ref3){var _ref4=_slicedToArray(_ref3,2),parentId=_ref4[0];_ref4[1]===smallestDistance&&(closestParentId=parentId)})),closestParentId},getDistanceToChild:function(parentId,childId){var distanceMemo=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,parentComponent=this.findComponent(parentId);if(parentComponent){var childIds=parentComponent.childIds;if(childIds.includes(childId))return distanceMemo;for(var i=0;i0&&void 0!==arguments[0]?arguments[0]:null;null===node&&(node=document);var allEls=Array.from(node.querySelectorAll("[wire\\:initial-data]")),onlyChildEls=Array.from(node.querySelectorAll("[wire\\:initial-data] [wire\\:initial-data]"));return allEls.filter((function(el){return!onlyChildEls.includes(el)}))},allModelElementsInside:function(root){return Array.from(root.querySelectorAll("[wire\\:model]"))},getByAttributeAndValue:function(attribute,value){return document.querySelector("[wire\\:".concat(attribute,'="').concat(value,'"]'))},nextFrame:function(fn){var _this=this;requestAnimationFrame((function(){requestAnimationFrame(fn.bind(_this))}))},closestRoot:function(el){return this.closestByAttribute(el,"id")},closestByAttribute:function(el,attribute){var closestEl=el.closest("[wire\\:".concat(attribute,"]"));if(!closestEl)throw"\nLivewire Error:\n\nCannot find parent element in DOM tree containing attribute: [wire:".concat(attribute,"].\n\nUsually this is caused by Livewire's DOM-differ not being able to properly track changes.\n\nReference the following guide for common causes: https://laravel-livewire.com/docs/troubleshooting \n\nReferenced element:\n\n").concat(el.outerHTML,"\n");return closestEl},isComponentRootEl:function(el){return this.hasAttribute(el,"id")},hasAttribute:function(el,attribute){return el.hasAttribute("wire:".concat(attribute))},getAttribute:function(el,attribute){return el.getAttribute("wire:".concat(attribute))},removeAttribute:function(el,attribute){return el.removeAttribute("wire:".concat(attribute))},setAttribute:function(el,attribute,value){return el.setAttribute("wire:".concat(attribute),value)},hasFocus:function(el){return el===document.activeElement},isInput:function(el){return["INPUT","TEXTAREA","SELECT"].includes(el.tagName.toUpperCase())},isTextInput:function(el){return["INPUT","TEXTAREA"].includes(el.tagName.toUpperCase())&&!["checkbox","radio"].includes(el.type)},valueFromInput:function(el,component){if("checkbox"===el.type){var modelName=wireDirectives(el).get("model").value,modelValue=component.deferredActions[modelName]?component.deferredActions[modelName].payload.value:getValue(component.data,modelName);return Array.isArray(modelValue)?this.mergeCheckboxValueIntoArray(el,modelValue):!!el.checked&&(el.getAttribute("value")||!0)}return"SELECT"===el.tagName&&el.multiple?this.getSelectValues(el):el.value},mergeCheckboxValueIntoArray:function(el,arrayValue){return el.checked?arrayValue.includes(el.value)?arrayValue:arrayValue.concat(el.value):arrayValue.filter((function(item){return item!=el.value}))},setInputValueFromModel:function(el,component){var modelString=wireDirectives(el).get("model").value,modelValue=getValue(component.data,modelString);"input"===el.tagName.toLowerCase()&&"file"===el.type||this.setInputValue(el,modelValue)},setInputValue:function(el,value){if(store$2.callHook("interceptWireModelSetValue",value,el),"radio"===el.type)el.checked=el.value==value;else if("checkbox"===el.type)if(Array.isArray(value)){var valueFound=!1;value.forEach((function(val){val==el.value&&(valueFound=!0)})),el.checked=valueFound}else el.checked=!!value;else"SELECT"===el.tagName?this.updateSelect(el,value):(value=void 0===value?"":value,el.value=value)},getSelectValues:function(el){return Array.from(el.options).filter((function(option){return option.selected})).map((function(option){return option.value||option.text}))},updateSelect:function(el,value){var arrayWrappedValue=[].concat(value).map((function(value){return value+""}));Array.from(el.options).forEach((function(option){option.selected=arrayWrappedValue.includes(option.value)}))}},ceil=Math.ceil,floor=Math.floor,toInteger=function(argument){return isNaN(argument=+argument)?0:(argument>0?floor:ceil)(argument)},requireObjectCoercible=function(it){if(null==it)throw TypeError("Can't call method on "+it);return it},createMethod$3=function(CONVERT_TO_STRING){return function($this,pos){var first,second,S=String(requireObjectCoercible($this)),position=toInteger(pos),size=S.length;return position<0||position>=size?CONVERT_TO_STRING?"":void 0:(first=S.charCodeAt(position))<55296||first>56319||position+1===size||(second=S.charCodeAt(position+1))<56320||second>57343?CONVERT_TO_STRING?S.charAt(position):first:CONVERT_TO_STRING?S.slice(position,position+2):second-56320+(first-55296<<10)+65536}},stringMultibyte={codeAt:createMethod$3(!1),charAt:createMethod$3(!0)},commonjsGlobal="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function createCommonjsModule(fn,basedir,module){return fn(module={path:basedir,exports:{},require:function(path,base){return commonjsRequire(path,null==base?module.path:base)}},module.exports),module.exports}function commonjsRequire(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}var check=function(it){return it&&it.Math==Math&&it},global_1=check("object"==typeof globalThis&&globalThis)||check("object"==typeof window&&window)||check("object"==typeof self&&self)||check("object"==typeof commonjsGlobal&&commonjsGlobal)||function(){return this}()||Function("return this")(),fails=function(exec){try{return!!exec()}catch(error){return!0}},descriptors=!fails((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]})),isObject=function(it){return"object"==typeof it?null!==it:"function"==typeof it},document$3=global_1.document,EXISTS=isObject(document$3)&&isObject(document$3.createElement),documentCreateElement=function(it){return EXISTS?document$3.createElement(it):{}},ie8DomDefine=!descriptors&&!fails((function(){return 7!=Object.defineProperty(documentCreateElement("div"),"a",{get:function(){return 7}}).a})),anObject=function(it){if(!isObject(it))throw TypeError(String(it)+" is not an object");return it},toPrimitive=function(input,PREFERRED_STRING){if(!isObject(input))return input;var fn,val;if(PREFERRED_STRING&&"function"==typeof(fn=input.toString)&&!isObject(val=fn.call(input)))return val;if("function"==typeof(fn=input.valueOf)&&!isObject(val=fn.call(input)))return val;if(!PREFERRED_STRING&&"function"==typeof(fn=input.toString)&&!isObject(val=fn.call(input)))return val;throw TypeError("Can't convert object to primitive value")},$defineProperty=Object.defineProperty,f$5=descriptors?$defineProperty:function(O,P,Attributes){if(anObject(O),P=toPrimitive(P,!0),anObject(Attributes),ie8DomDefine)try{return $defineProperty(O,P,Attributes)}catch(error){}if("get"in Attributes||"set"in Attributes)throw TypeError("Accessors not supported");return"value"in Attributes&&(O[P]=Attributes.value),O},objectDefineProperty={f:f$5},createPropertyDescriptor=function(bitmap,value){return{enumerable:!(1&bitmap),configurable:!(2&bitmap),writable:!(4&bitmap),value:value}},createNonEnumerableProperty=descriptors?function(object,key,value){return objectDefineProperty.f(object,key,createPropertyDescriptor(1,value))}:function(object,key,value){return object[key]=value,object},setGlobal=function(key,value){try{createNonEnumerableProperty(global_1,key,value)}catch(error){global_1[key]=value}return value},SHARED="__core-js_shared__",store$1=global_1[SHARED]||setGlobal(SHARED,{}),sharedStore=store$1,functionToString=Function.toString;"function"!=typeof sharedStore.inspectSource&&(sharedStore.inspectSource=function(it){return functionToString.call(it)});var inspectSource=sharedStore.inspectSource,WeakMap$1=global_1.WeakMap,nativeWeakMap="function"==typeof WeakMap$1&&/native code/.test(inspectSource(WeakMap$1)),toObject=function(argument){return Object(requireObjectCoercible(argument))},hasOwnProperty={}.hasOwnProperty,has$1=Object.hasOwn||function(it,key){return hasOwnProperty.call(toObject(it),key)},shared=createCommonjsModule((function(module){(module.exports=function(key,value){return sharedStore[key]||(sharedStore[key]=void 0!==value?value:{})})("versions",[]).push({version:"3.15.2",mode:"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})})),id=0,postfix=Math.random(),uid=function(key){return"Symbol("+String(void 0===key?"":key)+")_"+(++id+postfix).toString(36)},keys=shared("keys"),sharedKey=function(key){return keys[key]||(keys[key]=uid(key))},hiddenKeys$1={},OBJECT_ALREADY_INITIALIZED="Object already initialized",WeakMap=global_1.WeakMap,set$1,get,has,enforce=function(it){return has(it)?get(it):set$1(it,{})},getterFor=function(TYPE){return function(it){var state;if(!isObject(it)||(state=get(it)).type!==TYPE)throw TypeError("Incompatible receiver, "+TYPE+" required");return state}};if(nativeWeakMap||sharedStore.state){var store=sharedStore.state||(sharedStore.state=new WeakMap),wmget=store.get,wmhas=store.has,wmset=store.set;set$1=function(it,metadata){if(wmhas.call(store,it))throw new TypeError(OBJECT_ALREADY_INITIALIZED);return metadata.facade=it,wmset.call(store,it,metadata),metadata},get=function(it){return wmget.call(store,it)||{}},has=function(it){return wmhas.call(store,it)}}else{var STATE=sharedKey("state");hiddenKeys$1[STATE]=!0,set$1=function(it,metadata){if(has$1(it,STATE))throw new TypeError(OBJECT_ALREADY_INITIALIZED);return metadata.facade=it,createNonEnumerableProperty(it,STATE,metadata),metadata},get=function(it){return has$1(it,STATE)?it[STATE]:{}},has=function(it){return has$1(it,STATE)}}var internalState={set:set$1,get:get,has:has,enforce:enforce,getterFor:getterFor},$propertyIsEnumerable={}.propertyIsEnumerable,getOwnPropertyDescriptor$3=Object.getOwnPropertyDescriptor,NASHORN_BUG=getOwnPropertyDescriptor$3&&!$propertyIsEnumerable.call({1:2},1),f$4=NASHORN_BUG?function(V){var descriptor=getOwnPropertyDescriptor$3(this,V);return!!descriptor&&descriptor.enumerable}:$propertyIsEnumerable,objectPropertyIsEnumerable={f:f$4},toString={}.toString,classofRaw=function(it){return toString.call(it).slice(8,-1)},split="".split,indexedObject=fails((function(){return!Object("z").propertyIsEnumerable(0)}))?function(it){return"String"==classofRaw(it)?split.call(it,""):Object(it)}:Object,toIndexedObject=function(it){return indexedObject(requireObjectCoercible(it))},$getOwnPropertyDescriptor=Object.getOwnPropertyDescriptor,f$3=descriptors?$getOwnPropertyDescriptor:function(O,P){if(O=toIndexedObject(O),P=toPrimitive(P,!0),ie8DomDefine)try{return $getOwnPropertyDescriptor(O,P)}catch(error){}if(has$1(O,P))return createPropertyDescriptor(!objectPropertyIsEnumerable.f.call(O,P),O[P])},objectGetOwnPropertyDescriptor={f:f$3},redefine=createCommonjsModule((function(module){var getInternalState=internalState.get,enforceInternalState=internalState.enforce,TEMPLATE=String(String).split("String");(module.exports=function(O,key,value,options){var state,unsafe=!!options&&!!options.unsafe,simple=!!options&&!!options.enumerable,noTargetGet=!!options&&!!options.noTargetGet;"function"==typeof value&&("string"!=typeof key||has$1(value,"name")||createNonEnumerableProperty(value,"name",key),(state=enforceInternalState(value)).source||(state.source=TEMPLATE.join("string"==typeof key?key:""))),O!==global_1?(unsafe?!noTargetGet&&O[key]&&(simple=!0):delete O[key],simple?O[key]=value:createNonEnumerableProperty(O,key,value)):simple?O[key]=value:setGlobal(key,value)})(Function.prototype,"toString",(function(){return"function"==typeof this&&getInternalState(this).source||inspectSource(this)}))})),path=global_1,aFunction$1=function(variable){return"function"==typeof variable?variable:void 0},getBuiltIn=function(namespace,method){return arguments.length<2?aFunction$1(path[namespace])||aFunction$1(global_1[namespace]):path[namespace]&&path[namespace][method]||global_1[namespace]&&global_1[namespace][method]},min$2=Math.min,toLength=function(argument){return argument>0?min$2(toInteger(argument),9007199254740991):0},max=Math.max,min$1=Math.min,toAbsoluteIndex=function(index,length){var integer=toInteger(index);return integer<0?max(integer+length,0):min$1(integer,length)},createMethod$2=function(IS_INCLUDES){return function($this,el,fromIndex){var value,O=toIndexedObject($this),length=toLength(O.length),index=toAbsoluteIndex(fromIndex,length);if(IS_INCLUDES&&el!=el){for(;length>index;)if((value=O[index++])!=value)return!0}else for(;length>index;index++)if((IS_INCLUDES||index in O)&&O[index]===el)return IS_INCLUDES||index||0;return!IS_INCLUDES&&-1}},arrayIncludes={includes:createMethod$2(!0),indexOf:createMethod$2(!1)},indexOf=arrayIncludes.indexOf,objectKeysInternal=function(object,names){var key,O=toIndexedObject(object),i=0,result=[];for(key in O)!has$1(hiddenKeys$1,key)&&has$1(O,key)&&result.push(key);for(;names.length>i;)has$1(O,key=names[i++])&&(~indexOf(result,key)||result.push(key));return result},enumBugKeys=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],hiddenKeys=enumBugKeys.concat("length","prototype"),f$2=Object.getOwnPropertyNames||function(O){return objectKeysInternal(O,hiddenKeys)},objectGetOwnPropertyNames={f:f$2},f$1=Object.getOwnPropertySymbols,objectGetOwnPropertySymbols={f:f$1},ownKeys=getBuiltIn("Reflect","ownKeys")||function(it){var keys=objectGetOwnPropertyNames.f(anObject(it)),getOwnPropertySymbols=objectGetOwnPropertySymbols.f;return getOwnPropertySymbols?keys.concat(getOwnPropertySymbols(it)):keys},copyConstructorProperties=function(target,source){for(var keys=ownKeys(source),defineProperty=objectDefineProperty.f,getOwnPropertyDescriptor=objectGetOwnPropertyDescriptor.f,i=0;i=74)&&(match=engineUserAgent.match(/Chrome\/(\d+)/),match&&(version=match[1])));var engineV8Version=version&&+version,nativeSymbol=!!Object.getOwnPropertySymbols&&!fails((function(){var symbol=Symbol();return!String(symbol)||!(Object(symbol)instanceof Symbol)||!Symbol.sham&&engineV8Version&&engineV8Version<41})),useSymbolAsUid=nativeSymbol&&!Symbol.sham&&"symbol"==typeof Symbol.iterator,WellKnownSymbolsStore=shared("wks"),Symbol$1=global_1.Symbol,createWellKnownSymbol=useSymbolAsUid?Symbol$1:Symbol$1&&Symbol$1.withoutSetter||uid,wellKnownSymbol=function(name){return has$1(WellKnownSymbolsStore,name)&&(nativeSymbol||"string"==typeof WellKnownSymbolsStore[name])||(nativeSymbol&&has$1(Symbol$1,name)?WellKnownSymbolsStore[name]=Symbol$1[name]:WellKnownSymbolsStore[name]=createWellKnownSymbol("Symbol."+name)),WellKnownSymbolsStore[name]},ITERATOR$5=wellKnownSymbol("iterator"),BUGGY_SAFARI_ITERATORS$1=!1,returnThis$2=function(){return this},IteratorPrototype$2,PrototypeOfArrayIteratorPrototype,arrayIterator;[].keys&&(arrayIterator=[].keys(),"next"in arrayIterator?(PrototypeOfArrayIteratorPrototype=objectGetPrototypeOf(objectGetPrototypeOf(arrayIterator)),PrototypeOfArrayIteratorPrototype!==Object.prototype&&(IteratorPrototype$2=PrototypeOfArrayIteratorPrototype)):BUGGY_SAFARI_ITERATORS$1=!0);var NEW_ITERATOR_PROTOTYPE=null==IteratorPrototype$2||fails((function(){var test={};return IteratorPrototype$2[ITERATOR$5].call(test)!==test}));NEW_ITERATOR_PROTOTYPE&&(IteratorPrototype$2={}),has$1(IteratorPrototype$2,ITERATOR$5)||createNonEnumerableProperty(IteratorPrototype$2,ITERATOR$5,returnThis$2);var iteratorsCore={IteratorPrototype:IteratorPrototype$2,BUGGY_SAFARI_ITERATORS:BUGGY_SAFARI_ITERATORS$1},objectKeys=Object.keys||function(O){return objectKeysInternal(O,enumBugKeys)},objectDefineProperties=descriptors?Object.defineProperties:function(O,Properties){anObject(O);for(var key,keys=objectKeys(Properties),length=keys.length,index=0;length>index;)objectDefineProperty.f(O,key=keys[index++],Properties[key]);return O},html=getBuiltIn("document","documentElement"),GT=">",LT="<",PROTOTYPE="prototype",SCRIPT="script",IE_PROTO=sharedKey("IE_PROTO"),EmptyConstructor=function(){},scriptTag=function(content){return LT+SCRIPT+GT+content+LT+"/"+SCRIPT+GT},NullProtoObjectViaActiveX=function(activeXDocument){activeXDocument.write(scriptTag("")),activeXDocument.close();var temp=activeXDocument.parentWindow.Object;return activeXDocument=null,temp},NullProtoObjectViaIFrame=function(){var iframeDocument,iframe=documentCreateElement("iframe"),JS="java"+SCRIPT+":";return iframe.style.display="none",html.appendChild(iframe),iframe.src=String(JS),(iframeDocument=iframe.contentWindow.document).open(),iframeDocument.write(scriptTag("document.F=Object")),iframeDocument.close(),iframeDocument.F},activeXDocument,NullProtoObject=function(){try{activeXDocument=document.domain&&new ActiveXObject("htmlfile")}catch(error){}NullProtoObject=activeXDocument?NullProtoObjectViaActiveX(activeXDocument):NullProtoObjectViaIFrame();for(var length=enumBugKeys.length;length--;)delete NullProtoObject[PROTOTYPE][enumBugKeys[length]];return NullProtoObject()};hiddenKeys$1[IE_PROTO]=!0;var objectCreate=Object.create||function(O,Properties){var result;return null!==O?(EmptyConstructor[PROTOTYPE]=anObject(O),result=new EmptyConstructor,EmptyConstructor[PROTOTYPE]=null,result[IE_PROTO]=O):result=NullProtoObject(),void 0===Properties?result:objectDefineProperties(result,Properties)},defineProperty$1=objectDefineProperty.f,TO_STRING_TAG$3=wellKnownSymbol("toStringTag"),setToStringTag=function(it,TAG,STATIC){it&&!has$1(it=STATIC?it:it.prototype,TO_STRING_TAG$3)&&defineProperty$1(it,TO_STRING_TAG$3,{configurable:!0,value:TAG})},iterators={},IteratorPrototype$1=iteratorsCore.IteratorPrototype,returnThis$1=function(){return this},createIteratorConstructor=function(IteratorConstructor,NAME,next){var TO_STRING_TAG=NAME+" Iterator";return IteratorConstructor.prototype=objectCreate(IteratorPrototype$1,{next:createPropertyDescriptor(1,next)}),setToStringTag(IteratorConstructor,TO_STRING_TAG,!1),iterators[TO_STRING_TAG]=returnThis$1,IteratorConstructor},aPossiblePrototype=function(it){if(!isObject(it)&&null!==it)throw TypeError("Can't set "+String(it)+" as a prototype");return it},objectSetPrototypeOf=Object.setPrototypeOf||("__proto__"in{}?function(){var setter,CORRECT_SETTER=!1,test={};try{(setter=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(test,[]),CORRECT_SETTER=test instanceof Array}catch(error){}return function(O,proto){return anObject(O),aPossiblePrototype(proto),CORRECT_SETTER?setter.call(O,proto):O.__proto__=proto,O}}():void 0),IteratorPrototype=iteratorsCore.IteratorPrototype,BUGGY_SAFARI_ITERATORS=iteratorsCore.BUGGY_SAFARI_ITERATORS,ITERATOR$4=wellKnownSymbol("iterator"),KEYS="keys",VALUES="values",ENTRIES="entries",returnThis=function(){return this},defineIterator=function(Iterable,NAME,IteratorConstructor,next,DEFAULT,IS_SET,FORCED){createIteratorConstructor(IteratorConstructor,NAME,next);var CurrentIteratorPrototype,methods,KEY,getIterationMethod=function(KIND){if(KIND===DEFAULT&&defaultIterator)return defaultIterator;if(!BUGGY_SAFARI_ITERATORS&&KIND in IterablePrototype)return IterablePrototype[KIND];switch(KIND){case KEYS:case VALUES:case ENTRIES:return function(){return new IteratorConstructor(this,KIND)}}return function(){return new IteratorConstructor(this)}},TO_STRING_TAG=NAME+" Iterator",INCORRECT_VALUES_NAME=!1,IterablePrototype=Iterable.prototype,nativeIterator=IterablePrototype[ITERATOR$4]||IterablePrototype["@@iterator"]||DEFAULT&&IterablePrototype[DEFAULT],defaultIterator=!BUGGY_SAFARI_ITERATORS&&nativeIterator||getIterationMethod(DEFAULT),anyNativeIterator="Array"==NAME&&IterablePrototype.entries||nativeIterator;if(anyNativeIterator&&(CurrentIteratorPrototype=objectGetPrototypeOf(anyNativeIterator.call(new Iterable)),IteratorPrototype!==Object.prototype&&CurrentIteratorPrototype.next&&(objectGetPrototypeOf(CurrentIteratorPrototype)!==IteratorPrototype&&(objectSetPrototypeOf?objectSetPrototypeOf(CurrentIteratorPrototype,IteratorPrototype):"function"!=typeof CurrentIteratorPrototype[ITERATOR$4]&&createNonEnumerableProperty(CurrentIteratorPrototype,ITERATOR$4,returnThis)),setToStringTag(CurrentIteratorPrototype,TO_STRING_TAG,!0))),DEFAULT==VALUES&&nativeIterator&&nativeIterator.name!==VALUES&&(INCORRECT_VALUES_NAME=!0,defaultIterator=function(){return nativeIterator.call(this)}),IterablePrototype[ITERATOR$4]!==defaultIterator&&createNonEnumerableProperty(IterablePrototype,ITERATOR$4,defaultIterator),iterators[NAME]=defaultIterator,DEFAULT)if(methods={values:getIterationMethod(VALUES),keys:IS_SET?defaultIterator:getIterationMethod(KEYS),entries:getIterationMethod(ENTRIES)},FORCED)for(KEY in methods)(BUGGY_SAFARI_ITERATORS||INCORRECT_VALUES_NAME||!(KEY in IterablePrototype))&&redefine(IterablePrototype,KEY,methods[KEY]);else _export({target:NAME,proto:!0,forced:BUGGY_SAFARI_ITERATORS||INCORRECT_VALUES_NAME},methods);return methods},charAt=stringMultibyte.charAt,STRING_ITERATOR="String Iterator",setInternalState$2=internalState.set,getInternalState$2=internalState.getterFor(STRING_ITERATOR);defineIterator(String,"String",(function(iterated){setInternalState$2(this,{type:STRING_ITERATOR,string:String(iterated),index:0})}),(function(){var point,state=getInternalState$2(this),string=state.string,index=state.index;return index>=string.length?{value:void 0,done:!0}:(point=charAt(string,index),state.index+=point.length,{value:point,done:!1})}));var aFunction=function(it){if("function"!=typeof it)throw TypeError(String(it)+" is not a function");return it},functionBindContext=function(fn,that,length){if(aFunction(fn),void 0===that)return fn;switch(length){case 0:return function(){return fn.call(that)};case 1:return function(a){return fn.call(that,a)};case 2:return function(a,b){return fn.call(that,a,b)};case 3:return function(a,b,c){return fn.call(that,a,b,c)}}return function(){return fn.apply(that,arguments)}},iteratorClose=function(iterator){var returnMethod=iterator.return;if(void 0!==returnMethod)return anObject(returnMethod.call(iterator)).value},callWithSafeIterationClosing=function(iterator,fn,value,ENTRIES){try{return ENTRIES?fn(anObject(value)[0],value[1]):fn(value)}catch(error){throw iteratorClose(iterator),error}},ITERATOR$3=wellKnownSymbol("iterator"),ArrayPrototype$1=Array.prototype,isArrayIteratorMethod=function(it){return void 0!==it&&(iterators.Array===it||ArrayPrototype$1[ITERATOR$3]===it)},createProperty=function(object,key,value){var propertyKey=toPrimitive(key);propertyKey in object?objectDefineProperty.f(object,propertyKey,createPropertyDescriptor(0,value)):object[propertyKey]=value},TO_STRING_TAG$2=wellKnownSymbol("toStringTag"),test={};test[TO_STRING_TAG$2]="z";var toStringTagSupport="[object z]"===String(test),TO_STRING_TAG$1=wellKnownSymbol("toStringTag"),CORRECT_ARGUMENTS="Arguments"==classofRaw(function(){return arguments}()),tryGet=function(it,key){try{return it[key]}catch(error){}},classof=toStringTagSupport?classofRaw:function(it){var O,tag,result;return void 0===it?"Undefined":null===it?"Null":"string"==typeof(tag=tryGet(O=Object(it),TO_STRING_TAG$1))?tag:CORRECT_ARGUMENTS?classofRaw(O):"Object"==(result=classofRaw(O))&&"function"==typeof O.callee?"Arguments":result},ITERATOR$2=wellKnownSymbol("iterator"),getIteratorMethod=function(it){if(null!=it)return it[ITERATOR$2]||it["@@iterator"]||iterators[classof(it)]},arrayFrom=function(arrayLike){var length,result,step,iterator,next,value,O=toObject(arrayLike),C="function"==typeof this?this:Array,argumentsLength=arguments.length,mapfn=argumentsLength>1?arguments[1]:void 0,mapping=void 0!==mapfn,iteratorMethod=getIteratorMethod(O),index=0;if(mapping&&(mapfn=functionBindContext(mapfn,argumentsLength>2?arguments[2]:void 0,2)),null==iteratorMethod||C==Array&&isArrayIteratorMethod(iteratorMethod))for(result=new C(length=toLength(O.length));length>index;index++)value=mapping?mapfn(O[index],index):O[index],createProperty(result,index,value);else for(next=(iterator=iteratorMethod.call(O)).next,result=new C;!(step=next.call(iterator)).done;index++)value=mapping?callWithSafeIterationClosing(iterator,mapfn,[step.value,index],!0):step.value,createProperty(result,index,value);return result.length=index,result},ITERATOR$1=wellKnownSymbol("iterator"),SAFE_CLOSING=!1;try{var called=0,iteratorWithReturn={next:function(){return{done:!!called++}},return:function(){SAFE_CLOSING=!0}};iteratorWithReturn[ITERATOR$1]=function(){return this},Array.from(iteratorWithReturn,(function(){throw 2}))}catch(error){}var checkCorrectnessOfIteration=function(exec,SKIP_CLOSING){if(!SKIP_CLOSING&&!SAFE_CLOSING)return!1;var ITERATION_SUPPORT=!1;try{var object={};object[ITERATOR$1]=function(){return{next:function(){return{done:ITERATION_SUPPORT=!0}}}},exec(object)}catch(error){}return ITERATION_SUPPORT},INCORRECT_ITERATION$1=!checkCorrectnessOfIteration((function(iterable){Array.from(iterable)}));_export({target:"Array",stat:!0,forced:INCORRECT_ITERATION$1},{from:arrayFrom}),path.Array.from;var UNSCOPABLES=wellKnownSymbol("unscopables"),ArrayPrototype=Array.prototype;null==ArrayPrototype[UNSCOPABLES]&&objectDefineProperty.f(ArrayPrototype,UNSCOPABLES,{configurable:!0,value:objectCreate(null)});var addToUnscopables=function(key){ArrayPrototype[UNSCOPABLES][key]=!0},$includes=arrayIncludes.includes;_export({target:"Array",proto:!0},{includes:function(el){return $includes(this,el,arguments.length>1?arguments[1]:void 0)}}),addToUnscopables("includes");var call=Function.call,entryUnbind=function(CONSTRUCTOR,METHOD,length){return functionBindContext(call,global_1[CONSTRUCTOR].prototype[METHOD],length)};entryUnbind("Array","includes");var isArray=Array.isArray||function(arg){return"Array"==classofRaw(arg)},flattenIntoArray=function(target,original,source,sourceLen,start,depth,mapper,thisArg){for(var element,targetIndex=start,sourceIndex=0,mapFn=!!mapper&&functionBindContext(mapper,thisArg,3);sourceIndex0&&isArray(element))targetIndex=flattenIntoArray(target,original,element,toLength(element.length),targetIndex,depth-1)-1;else{if(targetIndex>=9007199254740991)throw TypeError("Exceed the acceptable array length");target[targetIndex]=element}targetIndex++}sourceIndex++}return targetIndex},flattenIntoArray_1=flattenIntoArray,SPECIES$3=wellKnownSymbol("species"),arraySpeciesCreate=function(originalArray,length){var C;return isArray(originalArray)&&("function"!=typeof(C=originalArray.constructor)||C!==Array&&!isArray(C.prototype)?isObject(C)&&null===(C=C[SPECIES$3])&&(C=void 0):C=void 0),new(void 0===C?Array:C)(0===length?0:length)};_export({target:"Array",proto:!0},{flat:function(){var depthArg=arguments.length?arguments[0]:void 0,O=toObject(this),sourceLen=toLength(O.length),A=arraySpeciesCreate(O,0);return A.length=flattenIntoArray_1(A,O,O,sourceLen,0,void 0===depthArg?1:toInteger(depthArg)),A}}),addToUnscopables("flat"),entryUnbind("Array","flat");var push=[].push,createMethod$1=function(TYPE){var IS_MAP=1==TYPE,IS_FILTER=2==TYPE,IS_SOME=3==TYPE,IS_EVERY=4==TYPE,IS_FIND_INDEX=6==TYPE,IS_FILTER_OUT=7==TYPE,NO_HOLES=5==TYPE||IS_FIND_INDEX;return function($this,callbackfn,that,specificCreate){for(var value,result,O=toObject($this),self=indexedObject(O),boundFunction=functionBindContext(callbackfn,that,3),length=toLength(self.length),index=0,create=specificCreate||arraySpeciesCreate,target=IS_MAP?create($this,length):IS_FILTER||IS_FILTER_OUT?create($this,0):void 0;length>index;index++)if((NO_HOLES||index in self)&&(result=boundFunction(value=self[index],index,O),TYPE))if(IS_MAP)target[index]=result;else if(result)switch(TYPE){case 3:return!0;case 5:return value;case 6:return index;case 2:push.call(target,value)}else switch(TYPE){case 4:return!1;case 7:push.call(target,value)}return IS_FIND_INDEX?-1:IS_SOME||IS_EVERY?IS_EVERY:target}},arrayIteration={forEach:createMethod$1(0),map:createMethod$1(1),filter:createMethod$1(2),some:createMethod$1(3),every:createMethod$1(4),find:createMethod$1(5),findIndex:createMethod$1(6),filterOut:createMethod$1(7)},$find=arrayIteration.find,FIND="find",SKIPS_HOLES=!0;FIND in[]&&Array(1)[FIND]((function(){SKIPS_HOLES=!1})),_export({target:"Array",proto:!0,forced:SKIPS_HOLES},{find:function(callbackfn){return $find(this,callbackfn,arguments.length>1?arguments[1]:void 0)}}),addToUnscopables(FIND),entryUnbind("Array","find");var $assign=Object.assign,defineProperty=Object.defineProperty,objectAssign=!$assign||fails((function(){if(descriptors&&1!==$assign({b:1},$assign(defineProperty({},"a",{enumerable:!0,get:function(){defineProperty(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var A={},B={},symbol=Symbol();return A[symbol]=7,"abcdefghijklmnopqrst".split("").forEach((function(chr){B[chr]=chr})),7!=$assign({},A)[symbol]||"abcdefghijklmnopqrst"!=objectKeys($assign({},B)).join("")}))?function(target,source){for(var T=toObject(target),argumentsLength=arguments.length,index=1,getOwnPropertySymbols=objectGetOwnPropertySymbols.f,propertyIsEnumerable=objectPropertyIsEnumerable.f;argumentsLength>index;)for(var key,S=indexedObject(arguments[index++]),keys=getOwnPropertySymbols?objectKeys(S).concat(getOwnPropertySymbols(S)):objectKeys(S),length=keys.length,j=0;length>j;)key=keys[j++],descriptors&&!propertyIsEnumerable.call(S,key)||(T[key]=S[key]);return T}:$assign;_export({target:"Object",stat:!0,forced:Object.assign!==objectAssign},{assign:objectAssign}),path.Object.assign;var propertyIsEnumerable=objectPropertyIsEnumerable.f,createMethod=function(TO_ENTRIES){return function(it){for(var key,O=toIndexedObject(it),keys=objectKeys(O),length=keys.length,i=0,result=[];length>i;)key=keys[i++],descriptors&&!propertyIsEnumerable.call(O,key)||result.push(TO_ENTRIES?[key,O[key]]:O[key]);return result}},objectToArray={entries:createMethod(!0),values:createMethod(!1)},$entries=objectToArray.entries;_export({target:"Object",stat:!0},{entries:function(O){return $entries(O)}}),path.Object.entries;var $values=objectToArray.values;_export({target:"Object",stat:!0},{values:function(O){return $values(O)}}),path.Object.values;var Result=function(stopped,result){this.stopped=stopped,this.result=result},iterate=function(iterable,unboundFunction,options){var iterator,iterFn,index,length,result,next,step,that=options&&options.that,AS_ENTRIES=!(!options||!options.AS_ENTRIES),IS_ITERATOR=!(!options||!options.IS_ITERATOR),INTERRUPTED=!(!options||!options.INTERRUPTED),fn=functionBindContext(unboundFunction,that,1+AS_ENTRIES+INTERRUPTED),stop=function(condition){return iterator&&iteratorClose(iterator),new Result(!0,condition)},callFn=function(value){return AS_ENTRIES?(anObject(value),INTERRUPTED?fn(value[0],value[1],stop):fn(value[0],value[1])):INTERRUPTED?fn(value,stop):fn(value)};if(IS_ITERATOR)iterator=iterable;else{if("function"!=typeof(iterFn=getIteratorMethod(iterable)))throw TypeError("Target is not iterable");if(isArrayIteratorMethod(iterFn)){for(index=0,length=toLength(iterable.length);length>index;index++)if((result=callFn(iterable[index]))&&result instanceof Result)return result;return new Result(!1)}iterator=iterFn.call(iterable)}for(next=iterator.next;!(step=next.call(iterator)).done;){try{result=callFn(step.value)}catch(error){throw iteratorClose(iterator),error}if("object"==typeof result&&result&&result instanceof Result)return result}return new Result(!1)},$AggregateError=function(errors,message){var that=this;if(!(that instanceof $AggregateError))return new $AggregateError(errors,message);objectSetPrototypeOf&&(that=objectSetPrototypeOf(new Error(void 0),objectGetPrototypeOf(that))),void 0!==message&&createNonEnumerableProperty(that,"message",String(message));var errorsArray=[];return iterate(errors,errorsArray.push,{that:errorsArray}),createNonEnumerableProperty(that,"errors",errorsArray),that};$AggregateError.prototype=objectCreate(Error.prototype,{constructor:createPropertyDescriptor(5,$AggregateError),message:createPropertyDescriptor(5,""),name:createPropertyDescriptor(5,"AggregateError")}),_export({global:!0},{AggregateError:$AggregateError});var objectToString=toStringTagSupport?{}.toString:function(){return"[object "+classof(this)+"]"};toStringTagSupport||redefine(Object.prototype,"toString",objectToString,{unsafe:!0});var nativePromiseConstructor=global_1.Promise,redefineAll=function(target,src,options){for(var key in src)redefine(target,key,src[key],options);return target},SPECIES$2=wellKnownSymbol("species"),setSpecies=function(CONSTRUCTOR_NAME){var Constructor=getBuiltIn(CONSTRUCTOR_NAME),defineProperty=objectDefineProperty.f;descriptors&&Constructor&&!Constructor[SPECIES$2]&&defineProperty(Constructor,SPECIES$2,{configurable:!0,get:function(){return this}})},anInstance=function(it,Constructor,name){if(!(it instanceof Constructor))throw TypeError("Incorrect "+(name?name+" ":"")+"invocation");return it},SPECIES$1=wellKnownSymbol("species"),speciesConstructor=function(O,defaultConstructor){var S,C=anObject(O).constructor;return void 0===C||null==(S=anObject(C)[SPECIES$1])?defaultConstructor:aFunction(S)},engineIsIos=/(?:iphone|ipod|ipad).*applewebkit/i.test(engineUserAgent),engineIsNode="process"==classofRaw(global_1.process),location=global_1.location,set=global_1.setImmediate,clear=global_1.clearImmediate,process$2=global_1.process,MessageChannel=global_1.MessageChannel,Dispatch=global_1.Dispatch,counter=0,queue={},ONREADYSTATECHANGE="onreadystatechange",defer,channel,port,run=function(id){if(queue.hasOwnProperty(id)){var fn=queue[id];delete queue[id],fn()}},runner=function(id){return function(){run(id)}},listener=function(event){run(event.data)},post=function(id){global_1.postMessage(id+"",location.protocol+"//"+location.host)};set&&clear||(set=function(fn){for(var args=[],i=1;arguments.length>i;)args.push(arguments[i++]);return queue[++counter]=function(){("function"==typeof fn?fn:Function(fn)).apply(void 0,args)},defer(counter),counter},clear=function(id){delete queue[id]},engineIsNode?defer=function(id){process$2.nextTick(runner(id))}:Dispatch&&Dispatch.now?defer=function(id){Dispatch.now(runner(id))}:MessageChannel&&!engineIsIos?(channel=new MessageChannel,port=channel.port2,channel.port1.onmessage=listener,defer=functionBindContext(port.postMessage,port,1)):global_1.addEventListener&&"function"==typeof postMessage&&!global_1.importScripts&&location&&"file:"!==location.protocol&&!fails(post)?(defer=post,global_1.addEventListener("message",listener,!1)):defer=ONREADYSTATECHANGE in documentCreateElement("script")?function(id){html.appendChild(documentCreateElement("script"))[ONREADYSTATECHANGE]=function(){html.removeChild(this),run(id)}}:function(id){setTimeout(runner(id),0)});var task$1={set:set,clear:clear},engineIsWebosWebkit=/web0s(?!.*chrome)/i.test(engineUserAgent),getOwnPropertyDescriptor$1=objectGetOwnPropertyDescriptor.f,macrotask=task$1.set,MutationObserver=global_1.MutationObserver||global_1.WebKitMutationObserver,document$2=global_1.document,process$1=global_1.process,Promise$1=global_1.Promise,queueMicrotaskDescriptor=getOwnPropertyDescriptor$1(global_1,"queueMicrotask"),queueMicrotask=queueMicrotaskDescriptor&&queueMicrotaskDescriptor.value,flush,head,last,notify$1,toggle,node,promise,then;queueMicrotask||(flush=function(){var parent,fn;for(engineIsNode&&(parent=process$1.domain)&&parent.exit();head;){fn=head.fn,head=head.next;try{fn()}catch(error){throw head?notify$1():last=void 0,error}}last=void 0,parent&&parent.enter()},engineIsIos||engineIsNode||engineIsWebosWebkit||!MutationObserver||!document$2?Promise$1&&Promise$1.resolve?(promise=Promise$1.resolve(void 0),promise.constructor=Promise$1,then=promise.then,notify$1=function(){then.call(promise,flush)}):notify$1=engineIsNode?function(){process$1.nextTick(flush)}:function(){macrotask.call(global_1,flush)}:(toggle=!0,node=document$2.createTextNode(""),new MutationObserver(flush).observe(node,{characterData:!0}),notify$1=function(){node.data=toggle=!toggle}));var microtask=queueMicrotask||function(fn){var task={fn:fn,next:void 0};last&&(last.next=task),head||(head=task,notify$1()),last=task},PromiseCapability=function(C){var resolve,reject;this.promise=new C((function($$resolve,$$reject){if(void 0!==resolve||void 0!==reject)throw TypeError("Bad Promise constructor");resolve=$$resolve,reject=$$reject})),this.resolve=aFunction(resolve),this.reject=aFunction(reject)},f=function(C){return new PromiseCapability(C)},newPromiseCapability$1={f:f},promiseResolve=function(C,x){if(anObject(C),isObject(x)&&x.constructor===C)return x;var promiseCapability=newPromiseCapability$1.f(C);return(0,promiseCapability.resolve)(x),promiseCapability.promise},hostReportErrors=function(a,b){var console=global_1.console;console&&console.error&&(1===arguments.length?console.error(a):console.error(a,b))},perform=function(exec){try{return{error:!1,value:exec()}}catch(error){return{error:!0,value:error}}},engineIsBrowser="object"==typeof window,task=task$1.set,SPECIES=wellKnownSymbol("species"),PROMISE="Promise",getInternalState$1=internalState.get,setInternalState$1=internalState.set,getInternalPromiseState=internalState.getterFor(PROMISE),NativePromisePrototype=nativePromiseConstructor&&nativePromiseConstructor.prototype,PromiseConstructor=nativePromiseConstructor,PromiseConstructorPrototype=NativePromisePrototype,TypeError$1=global_1.TypeError,document$1=global_1.document,process=global_1.process,newPromiseCapability=newPromiseCapability$1.f,newGenericPromiseCapability=newPromiseCapability,DISPATCH_EVENT=!!(document$1&&document$1.createEvent&&global_1.dispatchEvent),NATIVE_REJECTION_EVENT="function"==typeof PromiseRejectionEvent,UNHANDLED_REJECTION="unhandledrejection",REJECTION_HANDLED="rejectionhandled",PENDING=0,FULFILLED=1,REJECTED=2,HANDLED=1,UNHANDLED=2,SUBCLASSING=!1,Internal,OwnPromiseCapability,PromiseWrapper,nativeThen,FORCED=isForced_1(PROMISE,(function(){var PROMISE_CONSTRUCTOR_SOURCE=inspectSource(PromiseConstructor),GLOBAL_CORE_JS_PROMISE=PROMISE_CONSTRUCTOR_SOURCE!==String(PromiseConstructor);if(!GLOBAL_CORE_JS_PROMISE&&66===engineV8Version)return!0;if(engineV8Version>=51&&/native code/.test(PROMISE_CONSTRUCTOR_SOURCE))return!1;var promise=new PromiseConstructor((function(resolve){resolve(1)})),FakePromise=function(exec){exec((function(){}),(function(){}))};return(promise.constructor={})[SPECIES]=FakePromise,!(SUBCLASSING=promise.then((function(){}))instanceof FakePromise)||!GLOBAL_CORE_JS_PROMISE&&engineIsBrowser&&!NATIVE_REJECTION_EVENT})),INCORRECT_ITERATION=FORCED||!checkCorrectnessOfIteration((function(iterable){PromiseConstructor.all(iterable).catch((function(){}))})),isThenable=function(it){var then;return!(!isObject(it)||"function"!=typeof(then=it.then))&&then},notify=function(state,isReject){if(!state.notified){state.notified=!0;var chain=state.reactions;microtask((function(){for(var value=state.value,ok=state.state==FULFILLED,index=0;chain.length>index;){var result,then,exited,reaction=chain[index++],handler=ok?reaction.ok:reaction.fail,resolve=reaction.resolve,reject=reaction.reject,domain=reaction.domain;try{handler?(ok||(state.rejection===UNHANDLED&&onHandleUnhandled(state),state.rejection=HANDLED),!0===handler?result=value:(domain&&domain.enter(),result=handler(value),domain&&(domain.exit(),exited=!0)),result===reaction.promise?reject(TypeError$1("Promise-chain cycle")):(then=isThenable(result))?then.call(result,resolve,reject):resolve(result)):reject(value)}catch(error){domain&&!exited&&domain.exit(),reject(error)}}state.reactions=[],state.notified=!1,isReject&&!state.rejection&&onUnhandled(state)}))}},dispatchEvent=function(name,promise,reason){var event,handler;DISPATCH_EVENT?((event=document$1.createEvent("Event")).promise=promise,event.reason=reason,event.initEvent(name,!1,!0),global_1.dispatchEvent(event)):event={promise:promise,reason:reason},!NATIVE_REJECTION_EVENT&&(handler=global_1["on"+name])?handler(event):name===UNHANDLED_REJECTION&&hostReportErrors("Unhandled promise rejection",reason)},onUnhandled=function(state){task.call(global_1,(function(){var result,promise=state.facade,value=state.value;if(isUnhandled(state)&&(result=perform((function(){engineIsNode?process.emit("unhandledRejection",value,promise):dispatchEvent(UNHANDLED_REJECTION,promise,value)})),state.rejection=engineIsNode||isUnhandled(state)?UNHANDLED:HANDLED,result.error))throw result.value}))},isUnhandled=function(state){return state.rejection!==HANDLED&&!state.parent},onHandleUnhandled=function(state){task.call(global_1,(function(){var promise=state.facade;engineIsNode?process.emit("rejectionHandled",promise):dispatchEvent(REJECTION_HANDLED,promise,state.value)}))},bind=function(fn,state,unwrap){return function(value){fn(state,value,unwrap)}},internalReject=function(state,value,unwrap){state.done||(state.done=!0,unwrap&&(state=unwrap),state.value=value,state.state=REJECTED,notify(state,!0))},internalResolve=function(state,value,unwrap){if(!state.done){state.done=!0,unwrap&&(state=unwrap);try{if(state.facade===value)throw TypeError$1("Promise can't be resolved itself");var then=isThenable(value);then?microtask((function(){var wrapper={done:!1};try{then.call(value,bind(internalResolve,wrapper,state),bind(internalReject,wrapper,state))}catch(error){internalReject(wrapper,error,state)}})):(state.value=value,state.state=FULFILLED,notify(state,!1))}catch(error){internalReject({done:!1},error,state)}}};if(FORCED&&(PromiseConstructor=function(executor){anInstance(this,PromiseConstructor,PROMISE),aFunction(executor),Internal.call(this);var state=getInternalState$1(this);try{executor(bind(internalResolve,state),bind(internalReject,state))}catch(error){internalReject(state,error)}},PromiseConstructorPrototype=PromiseConstructor.prototype,Internal=function(executor){setInternalState$1(this,{type:PROMISE,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:PENDING,value:void 0})},Internal.prototype=redefineAll(PromiseConstructorPrototype,{then:function(onFulfilled,onRejected){var state=getInternalPromiseState(this),reaction=newPromiseCapability(speciesConstructor(this,PromiseConstructor));return reaction.ok="function"!=typeof onFulfilled||onFulfilled,reaction.fail="function"==typeof onRejected&&onRejected,reaction.domain=engineIsNode?process.domain:void 0,state.parent=!0,state.reactions.push(reaction),state.state!=PENDING&¬ify(state,!1),reaction.promise},catch:function(onRejected){return this.then(void 0,onRejected)}}),OwnPromiseCapability=function(){var promise=new Internal,state=getInternalState$1(promise);this.promise=promise,this.resolve=bind(internalResolve,state),this.reject=bind(internalReject,state)},newPromiseCapability$1.f=newPromiseCapability=function(C){return C===PromiseConstructor||C===PromiseWrapper?new OwnPromiseCapability(C):newGenericPromiseCapability(C)},"function"==typeof nativePromiseConstructor&&NativePromisePrototype!==Object.prototype)){nativeThen=NativePromisePrototype.then,SUBCLASSING||(redefine(NativePromisePrototype,"then",(function(onFulfilled,onRejected){var that=this;return new PromiseConstructor((function(resolve,reject){nativeThen.call(that,resolve,reject)})).then(onFulfilled,onRejected)}),{unsafe:!0}),redefine(NativePromisePrototype,"catch",PromiseConstructorPrototype.catch,{unsafe:!0}));try{delete NativePromisePrototype.constructor}catch(error){}objectSetPrototypeOf&&objectSetPrototypeOf(NativePromisePrototype,PromiseConstructorPrototype)}_export({global:!0,wrap:!0,forced:FORCED},{Promise:PromiseConstructor}),setToStringTag(PromiseConstructor,PROMISE,!1),setSpecies(PROMISE),PromiseWrapper=getBuiltIn(PROMISE),_export({target:PROMISE,stat:!0,forced:FORCED},{reject:function(r){var capability=newPromiseCapability(this);return capability.reject.call(void 0,r),capability.promise}}),_export({target:PROMISE,stat:!0,forced:FORCED},{resolve:function(x){return promiseResolve(this,x)}}),_export({target:PROMISE,stat:!0,forced:INCORRECT_ITERATION},{all:function(iterable){var C=this,capability=newPromiseCapability(C),resolve=capability.resolve,reject=capability.reject,result=perform((function(){var $promiseResolve=aFunction(C.resolve),values=[],counter=0,remaining=1;iterate(iterable,(function(promise){var index=counter++,alreadyCalled=!1;values.push(void 0),remaining++,$promiseResolve.call(C,promise).then((function(value){alreadyCalled||(alreadyCalled=!0,values[index]=value,--remaining||resolve(values))}),reject)})),--remaining||resolve(values)}));return result.error&&reject(result.value),capability.promise},race:function(iterable){var C=this,capability=newPromiseCapability(C),reject=capability.reject,result=perform((function(){var $promiseResolve=aFunction(C.resolve);iterate(iterable,(function(promise){$promiseResolve.call(C,promise).then(capability.resolve,reject)}))}));return result.error&&reject(result.value),capability.promise}}),_export({target:"Promise",stat:!0},{allSettled:function(iterable){var C=this,capability=newPromiseCapability$1.f(C),resolve=capability.resolve,reject=capability.reject,result=perform((function(){var promiseResolve=aFunction(C.resolve),values=[],counter=0,remaining=1;iterate(iterable,(function(promise){var index=counter++,alreadyCalled=!1;values.push(void 0),remaining++,promiseResolve.call(C,promise).then((function(value){alreadyCalled||(alreadyCalled=!0,values[index]={status:"fulfilled",value:value},--remaining||resolve(values))}),(function(error){alreadyCalled||(alreadyCalled=!0,values[index]={status:"rejected",reason:error},--remaining||resolve(values))}))})),--remaining||resolve(values)}));return result.error&&reject(result.value),capability.promise}});var PROMISE_ANY_ERROR="No one promise resolved";_export({target:"Promise",stat:!0},{any:function(iterable){var C=this,capability=newPromiseCapability$1.f(C),resolve=capability.resolve,reject=capability.reject,result=perform((function(){var promiseResolve=aFunction(C.resolve),errors=[],counter=0,remaining=1,alreadyResolved=!1;iterate(iterable,(function(promise){var index=counter++,alreadyRejected=!1;errors.push(void 0),remaining++,promiseResolve.call(C,promise).then((function(value){alreadyRejected||alreadyResolved||(alreadyResolved=!0,resolve(value))}),(function(error){alreadyRejected||alreadyResolved||(alreadyRejected=!0,errors[index]=error,--remaining||reject(new(getBuiltIn("AggregateError"))(errors,PROMISE_ANY_ERROR)))}))})),--remaining||reject(new(getBuiltIn("AggregateError"))(errors,PROMISE_ANY_ERROR))}));return result.error&&reject(result.value),capability.promise}});var NON_GENERIC=!!nativePromiseConstructor&&fails((function(){nativePromiseConstructor.prototype.finally.call({then:function(){}},(function(){}))}));if(_export({target:"Promise",proto:!0,real:!0,forced:NON_GENERIC},{finally:function(onFinally){var C=speciesConstructor(this,getBuiltIn("Promise")),isFunction="function"==typeof onFinally;return this.then(isFunction?function(x){return promiseResolve(C,onFinally()).then((function(){return x}))}:onFinally,isFunction?function(e){return promiseResolve(C,onFinally()).then((function(){throw e}))}:onFinally)}}),"function"==typeof nativePromiseConstructor){var method=getBuiltIn("Promise").prototype.finally;nativePromiseConstructor.prototype.finally!==method&&redefine(nativePromiseConstructor.prototype,"finally",method,{unsafe:!0})}var domIterables={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0},ARRAY_ITERATOR="Array Iterator",setInternalState=internalState.set,getInternalState=internalState.getterFor(ARRAY_ITERATOR),es_array_iterator=defineIterator(Array,"Array",(function(iterated,kind){setInternalState(this,{type:ARRAY_ITERATOR,target:toIndexedObject(iterated),index:0,kind:kind})}),(function(){var state=getInternalState(this),target=state.target,kind=state.kind,index=state.index++;return!target||index>=target.length?(state.target=void 0,{value:void 0,done:!0}):"keys"==kind?{value:index,done:!1}:"values"==kind?{value:target[index],done:!1}:{value:[index,target[index]],done:!1}}),"values");iterators.Arguments=iterators.Array,addToUnscopables("keys"),addToUnscopables("values"),addToUnscopables("entries");var ITERATOR=wellKnownSymbol("iterator"),TO_STRING_TAG=wellKnownSymbol("toStringTag"),ArrayValues=es_array_iterator.values;for(var COLLECTION_NAME in domIterables){var Collection=global_1[COLLECTION_NAME],CollectionPrototype=Collection&&Collection.prototype;if(CollectionPrototype){if(CollectionPrototype[ITERATOR]!==ArrayValues)try{createNonEnumerableProperty(CollectionPrototype,ITERATOR,ArrayValues)}catch(error){CollectionPrototype[ITERATOR]=ArrayValues}if(CollectionPrototype[TO_STRING_TAG]||createNonEnumerableProperty(CollectionPrototype,TO_STRING_TAG,COLLECTION_NAME),domIterables[COLLECTION_NAME])for(var METHOD_NAME in es_array_iterator)if(CollectionPrototype[METHOD_NAME]!==es_array_iterator[METHOD_NAME])try{createNonEnumerableProperty(CollectionPrototype,METHOD_NAME,es_array_iterator[METHOD_NAME])}catch(error){CollectionPrototype[METHOD_NAME]=es_array_iterator[METHOD_NAME]}}}path.Promise,_export({target:"Promise",stat:!0},{try:function(callbackfn){var promiseCapability=newPromiseCapability$1.f(this),result=perform(callbackfn);return(result.error?promiseCapability.reject:promiseCapability.resolve)(result.value),promiseCapability.promise}});var MATCH$1=wellKnownSymbol("match"),isRegexp=function(it){var isRegExp;return isObject(it)&&(void 0!==(isRegExp=it[MATCH$1])?!!isRegExp:"RegExp"==classofRaw(it))},notARegexp=function(it){if(isRegexp(it))throw TypeError("The method doesn't accept regular expressions");return it},MATCH=wellKnownSymbol("match"),correctIsRegexpLogic=function(METHOD_NAME){var regexp=/./;try{"/./"[METHOD_NAME](regexp)}catch(error1){try{return regexp[MATCH]=!1,"/./"[METHOD_NAME](regexp)}catch(error2){}}return!1},getOwnPropertyDescriptor=objectGetOwnPropertyDescriptor.f,$startsWith="".startsWith,min=Math.min,CORRECT_IS_REGEXP_LOGIC=correctIsRegexpLogic("startsWith"),MDN_POLYFILL_BUG=!(CORRECT_IS_REGEXP_LOGIC||(descriptor=getOwnPropertyDescriptor(String.prototype,"startsWith"),!descriptor||descriptor.writable)),descriptor;_export({target:"String",proto:!0,forced:!MDN_POLYFILL_BUG&&!CORRECT_IS_REGEXP_LOGIC},{startsWith:function(searchString){var that=String(requireObjectCoercible(this));notARegexp(searchString);var index=toLength(min(arguments.length>1?arguments[1]:void 0,that.length)),search=String(searchString);return $startsWith?$startsWith.call(that,search,index):that.slice(index,index+search.length)===search}}),entryUnbind("String","startsWith");var global$1="undefined"!=typeof globalThis&&globalThis||"undefined"!=typeof self&&self||void 0!==global$1&&global$1,support={searchParams:"URLSearchParams"in global$1,iterable:"Symbol"in global$1&&"iterator"in Symbol,blob:"FileReader"in global$1&&"Blob"in global$1&&function(){try{return new Blob,!0}catch(e){return!1}}(),formData:"FormData"in global$1,arrayBuffer:"ArrayBuffer"in global$1};function isDataView(obj){return obj&&DataView.prototype.isPrototypeOf(obj)}if(support.arrayBuffer)var viewClasses=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],isArrayBufferView=ArrayBuffer.isView||function(obj){return obj&&viewClasses.indexOf(Object.prototype.toString.call(obj))>-1};function normalizeName(name){if("string"!=typeof name&&(name=String(name)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name)||""===name)throw new TypeError('Invalid character in header field name: "'+name+'"');return name.toLowerCase()}function normalizeValue(value){return"string"!=typeof value&&(value=String(value)),value}function iteratorFor(items){var iterator={next:function(){var value=items.shift();return{done:void 0===value,value:value}}};return support.iterable&&(iterator[Symbol.iterator]=function(){return iterator}),iterator}function Headers(headers){this.map={},headers instanceof Headers?headers.forEach((function(value,name){this.append(name,value)}),this):Array.isArray(headers)?headers.forEach((function(header){this.append(header[0],header[1])}),this):headers&&Object.getOwnPropertyNames(headers).forEach((function(name){this.append(name,headers[name])}),this)}function consumed(body){if(body.bodyUsed)return Promise.reject(new TypeError("Already read"));body.bodyUsed=!0}function fileReaderReady(reader){return new Promise((function(resolve,reject){reader.onload=function(){resolve(reader.result)},reader.onerror=function(){reject(reader.error)}}))}function readBlobAsArrayBuffer(blob){var reader=new FileReader,promise=fileReaderReady(reader);return reader.readAsArrayBuffer(blob),promise}function readBlobAsText(blob){var reader=new FileReader,promise=fileReaderReady(reader);return reader.readAsText(blob),promise}function readArrayBufferAsText(buf){for(var view=new Uint8Array(buf),chars=new Array(view.length),i=0;i-1?upcased:method}function Request(input,options){if(!(this instanceof Request))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');var body=(options=options||{}).body;if(input instanceof Request){if(input.bodyUsed)throw new TypeError("Already read");this.url=input.url,this.credentials=input.credentials,options.headers||(this.headers=new Headers(input.headers)),this.method=input.method,this.mode=input.mode,this.signal=input.signal,body||null==input._bodyInit||(body=input._bodyInit,input.bodyUsed=!0)}else this.url=String(input);if(this.credentials=options.credentials||this.credentials||"same-origin",!options.headers&&this.headers||(this.headers=new Headers(options.headers)),this.method=normalizeMethod(options.method||this.method||"GET"),this.mode=options.mode||this.mode||null,this.signal=options.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&body)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(body),!("GET"!==this.method&&"HEAD"!==this.method||"no-store"!==options.cache&&"no-cache"!==options.cache)){var reParamSearch=/([?&])_=[^&]*/;if(reParamSearch.test(this.url))this.url=this.url.replace(reParamSearch,"$1_="+(new Date).getTime());else{this.url+=(/\?/.test(this.url)?"&":"?")+"_="+(new Date).getTime()}}}function decode(body){var form=new FormData;return body.trim().split("&").forEach((function(bytes){if(bytes){var split=bytes.split("="),name=split.shift().replace(/\+/g," "),value=split.join("=").replace(/\+/g," ");form.append(decodeURIComponent(name),decodeURIComponent(value))}})),form}function parseHeaders(rawHeaders){var headers=new Headers;return rawHeaders.replace(/\r?\n[\t ]+/g," ").split("\r").map((function(header){return 0===header.indexOf("\n")?header.substr(1,header.length):header})).forEach((function(line){var parts=line.split(":"),key=parts.shift().trim();if(key){var value=parts.join(":").trim();headers.append(key,value)}})),headers}function Response(bodyInit,options){if(!(this instanceof Response))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');options||(options={}),this.type="default",this.status=void 0===options.status?200:options.status,this.ok=this.status>=200&&this.status<300,this.statusText=void 0===options.statusText?"":""+options.statusText,this.headers=new Headers(options.headers),this.url=options.url||"",this._initBody(bodyInit)}Request.prototype.clone=function(){return new Request(this,{body:this._bodyInit})},Body.call(Request.prototype),Body.call(Response.prototype),Response.prototype.clone=function(){return new Response(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new Headers(this.headers),url:this.url})},Response.error=function(){var response=new Response(null,{status:0,statusText:""});return response.type="error",response};var redirectStatuses=[301,302,303,307,308];Response.redirect=function(url,status){if(-1===redirectStatuses.indexOf(status))throw new RangeError("Invalid status code");return new Response(null,{status:status,headers:{location:url}})};var DOMException=global$1.DOMException;try{new DOMException}catch(err){DOMException=function(message,name){this.message=message,this.name=name;var error=Error(message);this.stack=error.stack},DOMException.prototype=Object.create(Error.prototype),DOMException.prototype.constructor=DOMException}function fetch$1(input,init){return new Promise((function(resolve,reject){var request=new Request(input,init);if(request.signal&&request.signal.aborted)return reject(new DOMException("Aborted","AbortError"));var xhr=new XMLHttpRequest;function abortXhr(){xhr.abort()}xhr.onload=function(){var options={status:xhr.status,statusText:xhr.statusText,headers:parseHeaders(xhr.getAllResponseHeaders()||"")};options.url="responseURL"in xhr?xhr.responseURL:options.headers.get("X-Request-URL");var body="response"in xhr?xhr.response:xhr.responseText;setTimeout((function(){resolve(new Response(body,options))}),0)},xhr.onerror=function(){setTimeout((function(){reject(new TypeError("Network request failed"))}),0)},xhr.ontimeout=function(){setTimeout((function(){reject(new TypeError("Network request failed"))}),0)},xhr.onabort=function(){setTimeout((function(){reject(new DOMException("Aborted","AbortError"))}),0)},xhr.open(request.method,function(url){try{return""===url&&global$1.location.href?global$1.location.href:url}catch(e){return url}}(request.url),!0),"include"===request.credentials?xhr.withCredentials=!0:"omit"===request.credentials&&(xhr.withCredentials=!1),"responseType"in xhr&&(support.blob?xhr.responseType="blob":support.arrayBuffer&&request.headers.get("Content-Type")&&-1!==request.headers.get("Content-Type").indexOf("application/octet-stream")&&(xhr.responseType="arraybuffer")),!init||"object"!=typeof init.headers||init.headers instanceof Headers?request.headers.forEach((function(value,name){xhr.setRequestHeader(name,value)})):Object.getOwnPropertyNames(init.headers).forEach((function(name){xhr.setRequestHeader(name,normalizeValue(init.headers[name]))})),request.signal&&(request.signal.addEventListener("abort",abortXhr),xhr.onreadystatechange=function(){4===xhr.readyState&&request.signal.removeEventListener("abort",abortXhr)}),xhr.send(void 0===request._bodyInit?null:request._bodyInit)}))}fetch$1.polyfill=!0,global$1.fetch||(global$1.fetch=fetch$1,global$1.Headers=Headers,global$1.Request=Request,global$1.Response=Response),null==Element.prototype.getAttributeNames&&(Element.prototype.getAttributeNames=function(){for(var attributes=this.attributes,length=attributes.length,result=new Array(length),i=0;i=0&&matches.item(i)!==this;);return i>-1}),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(s){var el=this;do{if(el.matches(s))return el;el=el.parentElement||el.parentNode}while(null!==el&&1===el.nodeType);return null});var Connection=function(){function Connection(){_classCallCheck(this,Connection),this.headers={}}return _createClass(Connection,[{key:"onMessage",value:function(message,payload){message.component.receiveMessage(message,payload)}},{key:"onError",value:function(message,status,response){return message.component.messageSendFailed(),store$2.onErrorCallback(status,response)}},{key:"showExpiredMessage",value:function(response,message){store$2.sessionHasExpiredCallback?store$2.sessionHasExpiredCallback(response,message):confirm("This page has expired.\nWould you like to refresh the page?")&&window.location.reload()}},{key:"sendMessage",value:function(message){var _this=this,payload=message.payload(),csrfToken=getCsrfToken(),socketId=this.getSocketId();if(window.__testing_request_interceptor)return window.__testing_request_interceptor(payload,this);fetch("".concat(window.livewire_app_url,"/livewire/message/").concat(payload.fingerprint.name),{method:"POST",body:JSON.stringify(payload),credentials:"same-origin",headers:_objectSpread2(_objectSpread2(_objectSpread2({"Content-Type":"application/json",Accept:"text/html, application/xhtml+xml","X-Livewire":!0},this.headers),{},{Referer:window.location.href},csrfToken&&{"X-CSRF-TOKEN":csrfToken}),socketId&&{"X-Socket-ID":socketId})}).then((function(response){if(response.ok)response.text().then((function(response){_this.isOutputFromDump(response)?(_this.onError(message),_this.showHtmlModal(response)):_this.onMessage(message,JSON.parse(response))}));else{if(!1===_this.onError(message,response.status,response))return;if(419===response.status){if(store$2.sessionHasExpired)return;store$2.sessionHasExpired=!0,_this.showExpiredMessage(response,message)}else response.text().then((function(response){_this.showHtmlModal(response)}))}})).catch((function(){_this.onError(message)}))}},{key:"isOutputFromDump",value:function(output){return!!output.match(/