diff --git a/.gitignore b/.gitignore index a860fb777bdf..ed99e3129ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ yarn-error.log local_version.txt .env .phpunit.result.cache +_ide_helper.php /resources/assets/bower /public/logo diff --git a/app/Factory/ClientContactFactory.php b/app/Factory/ClientContactFactory.php index 20e4d69e96f4..9d6216452485 100644 --- a/app/Factory/ClientContactFactory.php +++ b/app/Factory/ClientContactFactory.php @@ -22,7 +22,7 @@ class ClientContactFactory $client_contact->first_name = ''; $client_contact->user_id = $user_id; $client_contact->company_id = $company_id; - $client_contact->contact_key = Str::random(40); + $client_contact->contact_key = Str::random(32); $client_contact->id = 0; $client_contact->send_email = true; diff --git a/app/Factory/VendorContactFactory.php b/app/Factory/VendorContactFactory.php index 05031eda3bbf..499377b1ac64 100644 --- a/app/Factory/VendorContactFactory.php +++ b/app/Factory/VendorContactFactory.php @@ -22,7 +22,7 @@ class VendorContactFactory $vendor_contact->first_name = ''; $vendor_contact->user_id = $user_id; $vendor_contact->company_id = $company_id; - $vendor_contact->contact_key = Str::random(40); + $vendor_contact->contact_key = Str::random(32); $vendor_contact->id = 0; return $vendor_contact; diff --git a/app/Http/Controllers/VendorPortal/InvitationController.php b/app/Http/Controllers/VendorPortal/InvitationController.php index 80bc02e5d5df..fcf88380d5e0 100644 --- a/app/Http/Controllers/VendorPortal/InvitationController.php +++ b/app/Http/Controllers/VendorPortal/InvitationController.php @@ -91,7 +91,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/Models/Client.php b/app/Models/Client.php index 173bd945310f..288e68c374da 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -327,21 +327,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 20582ad23ab0..3c5d7ddc095c 100644 --- a/app/Models/Vendor.php +++ b/app/Models/Vendor.php @@ -169,6 +169,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/PdfBuilder.php b/app/Services/Pdf/PdfBuilder.php new file mode 100644 index 000000000000..af43a120f584 --- /dev/null +++ b/app/Services/Pdf/PdfBuilder.php @@ -0,0 +1,1573 @@ +service = $service; + + $this->commonmark = new CommonMarkConverter([ + 'allow_unsafe_links' => false, + ]); + } + + /** + * Builds the template sections + * + * @return self + * + */ + public function build(): self + { + $this->getTemplate() + ->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 string + */ + public function getCompiledHTML($final = false) + { + $html = $this->document->saveHTML(); + + return str_replace('%24', '$', $html); + } + + /** + * Generate the template + * + * @return self + * + */ + private function getTemplate() :self + { + $document = new DOMDocument(); + + $document->validateOnParse = true; + + @$document->loadHTML(mb_convert_encoding($this->service->designer->template, 'HTML-ENTITIES', 'UTF-8')); + + $this->document = $document; + + // $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; + } + + 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([ + '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; + } + + /** + * Generates statement sections + * + * @return self + * + */ + private function getStatementSections(): self + { + $this->genericSectionBuilder(); + + $this->mergeSections([ + '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; + } + + /** + * Parent method for building invoice table totals + * for statements. + * + * @return array + */ + public function statementInvoiceTableTotals(): array + { + $outstanding = $this->service->options['invoices']->sum('balance'); + + return [ + ['element' => 'p', 'content' => '$outstanding_label: ' . $this->service->config->formatMoney($outstanding)], + ]; + } + + + /** + * Parent method for building payments table within statement. + * + * @return array + */ + public function statementPaymentTable(): array + { + if (is_null($this->service->options['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->date_format, $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $payment->type ? $payment->type->name : ctrans('texts.manual_entry')]; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($payment->pivot->amount) ?: ' ']; + + $tbody[] = $element; + + $this->payment_amount_total += $payment->pivot->amount; + } + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('statement_payment')], + ['element' => 'tbody', 'elements' => $tbody], + ]; + } + + /** + * Generates the statement payments table + * + * @return array + * + */ + 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'), $this->service->config->formatMoney($this->payment_amount_total))], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_method'), $payment->type ? $payment->type->name : ctrans('texts.manual_entry'))], + ['element' => 'p', 'content' => \sprintf('%s: %s', ctrans('texts.payment_date'), $this->translateDate($payment->date, $this->service->config->date_format, $this->service->config->locale) ?: ' ')], + ]; + } + + /** + * 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 []; + } + + $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; + } + + + /** + * Generates the purchase order sections + * + * @return self + * + */ + private function getPurchaseOrderSections(): self + { + $this->genericSectionBuilder() + ->getProductAndTaskTables() + ->getProductTotals(); + + $this->mergeSections([ + 'vendor-details' => [ + 'id' => 'vendor-details', + 'elements' => $this->vendorDetails(), + ], + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->purchaseOrderDetails(), + ], + ]); + + 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', + 'elements' => $this->companyDetails(), + ], + 'company-address' => [ + 'id' => 'company-address', + 'elements' => $this->companyAddress(), + ], + 'footer-elements' => [ + 'id' => 'footer', + 'elements' => [ + $this->sharedFooterElements(), + ], + ], + ]); + + return $this; + } + + /** + * Generates the invoices table for statements + * + * @return array + * + */ + 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->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->translateDate($invoice->due_date, $this->service->config->client->date_format(), $this->service->config->locale) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->amount) ?: ' ']; + $element['elements'][] = ['element' => 'td', 'content' => $this->service->config->formatMoney($invoice->balance) ?: ' ']; + + $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->service->config->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->service->options) && + !empty($this->service->options[$type]) && + !is_null($this->service->options[$type]) + ) { + $document = new DOMDocument(); + $document->loadHTML($this->service->options[$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->service->config->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']]; + } 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']]; + } + } + } + + $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 = []; + + $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->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); + $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'] = $this->service->config->formatMoney($item->quantity); + + $data[$key][$table_type.'.unit_cost'] = $this->service->config->formatMoney($item->cost); + + $data[$key][$table_type.'.cost'] = $this->service->config->formatMoney($item->cost); + + $data[$key][$table_type.'.line_total'] = $this->service->config->formatMoney($item->line_total); + } 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) ? '' : $this->service->config->formatMoney($item->gross_line_total); + } else { + $data[$key][$table_type.'.gross_line_total'] = ''; + } + + if (property_exists($item, 'tax_amount')) { + $data[$key][$table_type.'.tax_amount'] = ($item->tax_amount == 0) ? '' : $this->service->config->formatMoney($item->tax_amount); + } else { + $data[$key][$table_type.'.tax_amount'] = ''; + } + + if (isset($item->discount) && $item->discount > 0) { + if ($item->is_amount_discount) { + $data[$key][$table_type.'.discount'] = $this->service->config->formatMoney($item->discount); + } 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); + + $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->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->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->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->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->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->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); + } + } + } + + /** + * 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. + $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], + ]]; + } + + /** + * Generates the totals table for + * the product type entities + * + * @return self + * + */ + private function getProductTotals(): self + { + $this->mergeSections([ + 'table-totals' => [ + 'id' => 'table-totals', + 'elements' => $this->getTableTotals(), + ], + ]); + + return $this; + } + + /** + * Generates the entity details for + * Credits + * Quotes + * Invoices + * + * @return self + * + */ + private function getProductEntityDetails(): self + { + 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([ + 'entity-details' => [ + 'id' => 'entity-details', + 'elements' => $this->quoteDetails(), + ], + ]); + } 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' => [ + ['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->service->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']], + ]], + ]], + ]; + } + + /** + * 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. + 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->service->config->entity->{$_variable})) { + return true; + } + + if (empty($this->service->config->entity->{$_variable})) { + return true; + } + + 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 + { + //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' => ['$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->service->document_type == PdfService::DELIVERY_NOTE) { + return $elements; + } + + 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']); + } + + if ($this->service->config->entity->partial > 0) { + $variables[] = '$partial_due'; + } + } + + 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']); + } + } + + 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->total_tax_map; + + 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' => $this->service->config->formatMoney($tax['total']), 'properties' => ['data-ref' => 'totals-table-total_tax_' . $i]], + ]]; + } + } elseif ($variable == '$line_taxes') { + $taxes = $this->service->config->entity->tax_map; + + 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' => $this->service->config->formatMoney($tax['total']), '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->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']], + ['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; + } + + /** + * Generates the product and task tables + * + * @return self + * + */ + public function getProductAndTaskTables(): self + { + $this->mergeSections([ + 'product-table' => [ + 'id' => 'product-table', + 'elements' => $this->productTable(), + ], + 'task-table' => [ + 'id' => 'task-table', + 'elements' => $this->taskTable(), + ], + ]); + + return $this; + } + + /** + * Generates the client details + * + * @return self + * + */ + public function getClientDetails(): self + { + $this->mergeSections([ + 'client-details' => [ + 'id' => 'client-details', + 'elements' => $this->clientDetails(), + ], + ]); + + return $this; + } + + /** + * Generates the product 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 []; + } + + return [ + ['element' => 'thead', 'elements' => $this->buildTableHeader('product')], + ['element' => 'tbody', 'elements' => $this->buildTableBody('$product')], + ]; + } + + /** + * Generates the task 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 []; + } + + return [ + ['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->date_format, $this->service->config->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' => $this->service->config->formatMoney($this->service->options['invoices']->sum('balance'))], + ]], + ]; + } + + /** + * Generates the invoice details + * + * @return array + * + */ + public function invoiceDetails(): array + { + $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) { + $variables[] = '$quote.balance_due'; + } + + 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) { + return !in_array($m, ['$invoice.balance_due', '$invoice.total']); + }); + + return $this->genericDetailsBuilder($variables); + } + + /** + * Generates the custom values for the + * entity. + * + * @param array + * @return array + */ + 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; + } + + + /** + * Generates the client delivery + * details array + * + * @return array + * + */ + 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; + } + + /** + * Generates the client details section + * + * @return array + */ + 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; + } + + /** + * 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']], + ['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->service->document_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(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']; + + $elements = []; + + foreach ($variables as $variable) { + $elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]]; + } + + return $elements; + } + + /** + * + * Generates an array of the company address + * + * @return array + * + */ + 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; + } + + /** + * + * Generates an array of vendor details + * + * @return array + * + */ + 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; + } + + + //////////////////////////////////////// + // 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($element['properties'])) { + foreach ($element['properties'] as $property => $value) { + $this->updateElementProperty($node, $property, $value); + } + } + + if (isset($element['elements'])) { + $this->createElementContent($node, $element['elements']); + } + } + + 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 ($this->service->company->markdown_enabled && array_key_exists('content', $child)) { + $child['content'] = str_replace('
', "\r", $child['content']); + $child['content'] = $this->commonmark->convert($child['content'] ?? ''); + } + } + + if (isset($child['content'])) { + if (isset($child['is_empty']) && $child['is_empty'] === true) { + continue; + } + + $contains_html = preg_match('#(?<=<)\w+(?=[^<]*?>)#', $child['content'], $m) != 0; + } + + if ($contains_html) { + // If the element contains the HTML, we gonna display it as is. Backend is going to + // encode it for us, preventing any errors on the processing stage. + // Later, we decode this using Javascript so it looks like it's normal HTML being injected. + // To get all elements that need frontend decoding, we use 'data-state' property. + + $_child = $this->document->createElement($child['element'], ''); + $_child->setAttribute('data-state', 'encoded-html'); + $_child->nodeValue = htmlspecialchars($child['content']); + } else { + // .. in case string doesn't contain any HTML, we'll just return + // raw $content. + + $_child = $this->document->createElement($child['element'], isset($child['content']) ? htmlspecialchars($child['content']) : ''); + } + + $element->appendChild($_child); + + if (isset($child['properties'])) { + foreach ($child['properties'] as $property => $value) { + $this->updateElementProperty($_child, $property, $value); + } + } + + if (isset($child['elements'])) { + $this->createElementContent($_child, $child['elements']); + } + } + + return $this; + } + + public function updateVariables() + { + $html = strtr($this->getCompiledHTML(), $this->service->html_variables['labels']); + + $html = strtr($html, $this->service->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->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->html_variables['values']); + if ($value === '' || $value === ' ') { + $child['is_empty'] = true; + } + } + + if (isset($child['elements'])) { + $this->getEmptyChildrens($child['elements']); + } + } + + return $this; + } +} diff --git a/app/Services/Pdf/PdfConfiguration.php b/app/Services/Pdf/PdfConfiguration.php new file mode 100644 index 000000000000..614b1cab8b99 --- /dev/null +++ b/app/Services/Pdf/PdfConfiguration.php @@ -0,0 +1,352 @@ +setEntityType() + ->setDateFormat() + ->setPdfVariables() + ->setDesign() + ->setCurrencyForPdf() + ->setLocale(); + + return $this; + } + + /** + * setLocale + * + * @return self + */ + private function setLocale(): self + { + App::forgetInstance('translator'); + + $t = app('translator'); + + App::setLocale($this->settings_object->locale()); + + $t->replace(Ninja::transformTranslations($this->settings)); + + $this->locale = $this->settings_object->locale(); + + return $this; + } + + /** + * setCurrency + * + * @return self + */ + private function setCurrencyForPdf(): self + { + $this->currency = $this->client ? $this->client->currency() : $this->vendor->currency(); + + $this->currency_entity = $this->client ? $this->client : $this->vendor; + + return $this; + } + + /** + * setPdfVariables + * + * @return self + */ + public function setPdfVariables() :self + { + $default = (array) CompanySettings::getEntityVariableDefaults(); + + // $variables = (array)$this->service->company->settings->pdf_variables; + $variables = (array)$this->settings->pdf_variables; + + foreach ($default as $property => $value) { + if (array_key_exists($property, $variables)) { + continue; + } + + $variables[$property] = $value; + } + + $this->pdf_variables = $variables; + + return $this; + } + + /** + * setEntityType + * + * @return self + */ + private function setEntityType(): self + { + $entity_design_id = ''; + + if ($this->service->invitation instanceof InvoiceInvitation) { + $this->entity = $this->service->invitation->invoice; + $this->entity_string = '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; + $this->country = $this->client->country; + } 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; + $this->country = $this->client->country; + } 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; + $this->country = $this->client->country; + } 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; + $this->country = $this->client->country; + } 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->company->settings; + $this->settings_object = $this->vendor; + $this->client = null; + $this->country = $this->vendor->country ?: $this->vendor->company->country(); + } else { + throw new \Exception('Unable to resolve entity', 500); + } + + $this->setTaxMap($this->entity->calc()->getTaxMap()); + $this->setTotalTaxMap($this->entity->calc()->getTotalTaxMap()); + + $this->path = $this->path.$this->entity->numberFormatter().'.pdf'; + + return $this; + } + + public function setTaxMap($map): self + { + $this->tax_map = $map; + + return $this; + } + + public function setTotalTaxMap($map): self + { + $this->total_tax_map = $map; + + return $this; + } + + public function setCurrency(Currency $currency): self + { + $this->currency = $currency; + + return $this; + } + + public function setCountry(Country $country): self + { + $this->country = $country; + + return $this; + } + + /** + * setDesign + * + * @return self + */ + private function setDesign(): self + { + $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; + } + + /** + * formatMoney + * + * @param float $value + * @return string + */ + public function formatMoney($value): string + { + $value = floatval($value); + + $thousand = $this->currency->thousand_separator; + $decimal = $this->currency->decimal_separator; + $precision = $this->currency->precision; + $code = $this->currency->code; + $swapSymbol = $this->currency->swap_currency_symbol; + + if (isset($this->country->thousand_separator) && strlen($this->country->thousand_separator) >= 1) { + $thousand = $this->country->thousand_separator; + } + + if (isset($this->country->decimal_separator) && strlen($this->country->decimal_separator) >= 1) { + $decimal = $this->country->decimal_separator; + } + + if (isset($this->country->swap_currency_symbol) && strlen($this->country->swap_currency_symbol) >= 1) { + $swapSymbol = $this->country->swap_currency_symbol; + } + + $value = number_format($value, $precision, $decimal, $thousand); + $symbol = $this->currency->symbol; + + if ($this->settings->show_currency_code === true && $this->currency->code == 'CHF') { + return "{$code} {$value}"; + } elseif ($this->settings->show_currency_code === true) { + return "{$value} {$code}"; + } elseif ($swapSymbol) { + return "{$value} ".trim($symbol); + } elseif ($this->settings->show_currency_code === false) { + return "{$symbol}{$value}"; + } else { + + $value = floatval($value); + $thousand = $this->currency->thousand_separator; + $decimal = $this->currency->decimal_separator; + $precision = $this->currency->precision; + + return number_format($value, $precision, $decimal, $thousand); + } + + } + + /** + * date_format + * + * @return self + */ + public function setDateFormat(): self + { + $date_formats = Cache::get('date_formats'); + + if (! $date_formats) { + $this->buildCache(true); + } + + $this->date_format = $date_formats->filter(function ($item) { + return $item->id == $this->settings->date_format_id; + })->first()->format; + + return $this; + } + + +} diff --git a/app/Services/Pdf/PdfDesigner.php b/app/Services/Pdf/PdfDesigner.php new file mode 100644 index 000000000000..5db92b32a390 --- /dev/null +++ b/app/Services/Pdf/PdfDesigner.php @@ -0,0 +1,73 @@ +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 + */ + + /** + * Returns the custom HTML design as + * a string + * + * @param array + * @return string + * + */ + private function composeFromPartials(array $partials) :string + { + $html = ''; + + $html .= $partials['includes']; + $html .= $partials['header']; + $html .= $partials['body']; + $html .= $partials['footer']; + + return $html; + } +} diff --git a/app/Services/Pdf/PdfMock.php b/app/Services/Pdf/PdfMock.php new file mode 100644 index 000000000000..af9e55ec2270 --- /dev/null +++ b/app/Services/Pdf/PdfMock.php @@ -0,0 +1,665 @@ +make(); + $mock->client = Client::factory()->make(); + $mock->tax_map = $this->getTaxMap(); + $mock->total_tax_map = $this->getTotalTaxMap(); + $mock->invitation = InvoiceInvitation::factory()->make(); + $mock->invitation->company = Company::factory()->make(); + $mock->invitation->company->account = Account::factory()->make(); + + return $mock; + + } + + private function getTaxMap() + { + + return collect( [['name' => 'GST', 'total' => 10]]); + + } + + private function getTotalTaxMap() + { + return [['name' => 'GST', 'total' => 10]]; + } + + public function getStubVariables() + { + return ['values' => + [ + '$client.shipping_postal_code' => '46420', + '$client.billing_postal_code' => '11243', + '$company.city_state_postal' => '90210', + '$company.postal_city_state' => 'CA', + '$product.gross_line_total' => '100', + '$client.postal_city_state' => '11243 Aufderharchester, North Carolina', + '$client.shipping_address1' => '453', + '$client.shipping_address2' => '66327 Waters Trail', + '$client.city_state_postal' => 'Aufderharchester, North Carolina 11243', + '$client.shipping_address' => '453
66327 Waters Trail
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.billing_address2' => '63993 Aiyana View', + '$client.billing_address1' => '8447', + '$client.shipping_country' => 'USA', + '$invoiceninja.whitelabel' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', + '$client.billing_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.billing_country' => 'USA', + '$task.gross_line_total' => '100', + '$contact.portal_button' => 'View client portal', + '$client.shipping_state' => 'Delaware', + '$invoice.public_notes' => 'These are some public notes for your document', + '$client.shipping_city' => 'Kesslerport', + '$client.billing_state' => 'North Carolina', + '$product.description' => 'A Product Description', + '$product.product_key' => 'A Product Key', + '$entity.public_notes' => 'Entity Public notes', + '$invoice.balance_due' => '$0.00', + '$client.public_notes' => ' ', + '$company.postal_code' => ' ', + '$client.billing_city' => 'Aufderharchester', + '$secondary_font_name' => 'Roboto', + '$product.line_total' => '', + '$product.tax_amount' => '', + '$company.vat_number' => ' ', + '$invoice.invoice_no' => '0029', + '$quote.quote_number' => '0029', + '$client.postal_code' => '11243', + '$contact.first_name' => 'Benedict', + '$secondary_font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', + '$contact.signature' => '', + '$product.tax_name1' => '', + '$product.tax_name2' => '', + '$product.tax_name3' => '', + '$product.unit_cost' => '', + '$quote.valid_until' => '2023-10-24', + '$custom_surcharge1' => '$0.00', + '$custom_surcharge2' => '$0.00', + '$custom_surcharge3' => '$0.00', + '$custom_surcharge4' => '$0.00', + '$quote.balance_due' => '$0.00', + '$company.id_number' => ' ', + '$invoice.po_number' => ' ', + '$invoice_total_raw' => 0.0, + '$postal_city_state' => '11243 Aufderharchester, North Carolina', + '$client.vat_number' => '975977515', + '$city_state_postal' => 'Aufderharchester, North Carolina 11243', + '$contact.full_name' => 'Benedict Eichmann', + '$contact.last_name' => 'Eichmann', + '$company.country_2' => 'US', + '$product.product1' => '', + '$product.product2' => '', + '$product.product3' => '', + '$product.product4' => '', + '$statement_amount' => '', + '$task.description' => '', + '$product.discount' => '', + '$entity_issued_to' => '', + '$assigned_to_user' => '', + '$product.quantity' => '', + '$total_tax_labels' => '', + '$total_tax_values' => '', + '$invoice.discount' => '$0.00', + '$invoice.subtotal' => '$0.00', + '$company.address2' => ' ', + '$partial_due_date' => ' ', + '$invoice.due_date' => ' ', + '$client.id_number' => ' ', + '$credit.po_number' => ' ', + '$company.address1' => ' ', + '$credit.credit_no' => '0029', + '$invoice.datetime' => '25/Feb/2023 1:10 am', + '$contact.custom1' => NULL, + '$contact.custom2' => NULL, + '$contact.custom3' => NULL, + '$contact.custom4' => NULL, + '$task.line_total' => '', + '$line_tax_labels' => '', + '$line_tax_values' => '', + '$secondary_color' => '#7081e0', + '$invoice.balance' => '$0.00', + '$invoice.custom1' => ' ', + '$invoice.custom2' => ' ', + '$invoice.custom3' => ' ', + '$invoice.custom4' => ' ', + '$company.custom1' => ' ', + '$company.custom2' => ' ', + '$company.custom3' => ' ', + '$company.custom4' => ' ', + '$quote.po_number' => ' ', + '$company.website' => ' ', + '$balance_due_raw' => '0.00', + '$entity.datetime' => '25/Feb/2023 1:10 am', + '$credit.datetime' => '25/Feb/2023 1:10 am', + '$client.address2' => '63993 Aiyana View', + '$client.address1' => '8447', + '$user.first_name' => 'Derrick Monahan DDS', + '$created_by_user' => 'Derrick Monahan DDS Erna Wunsch', + '$client.currency' => 'USD', + '$company.country' => 'United States', + '$company.address' => 'United States
', + '$tech_hero_image' => 'http://ninja.test:8000/images/pdf-designs/tech-hero-image.jpg', + '$task.tax_name1' => '', + '$task.tax_name2' => '', + '$task.tax_name3' => '', + '$client.balance' => '$0.00', + '$client_balance' => '$0.00', + '$credit.balance' => '$0.00', + '$credit_balance' => '$0.00', + '$gross_subtotal' => '$0.00', + '$invoice.amount' => '$0.00', + '$client.custom1' => ' ', + '$client.custom2' => ' ', + '$client.custom3' => ' ', + '$client.custom4' => ' ', + '$emailSignature' => ' ', + '$invoice.number' => '0029', + '$quote.quote_no' => '0029', + '$quote.datetime' => '25/Feb/2023 1:10 am', + '$client_address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$client.address' => '8447
63993 Aiyana View
Aufderharchester, North Carolina 11243
Afghanistan
', + '$payment_button' => 'Pay Now', + '$payment_qrcode' => ' + + +', + '$client.country' => 'Afghanistan', + '$user.last_name' => 'Erna Wunsch', + '$client.website' => 'http://www.parisian.org/', + '$dir_text_align' => 'left', + '$entity_images' => '', + '$task.discount' => '', + '$contact.email' => '', + '$primary_color' => '#298AAB', + '$credit_amount' => '$0.00', + '$invoice.total' => '$0.00', + '$invoice.taxes' => '$0.00', + '$quote.custom1' => ' ', + '$quote.custom2' => ' ', + '$quote.custom3' => ' ', + '$quote.custom4' => ' ', + '$company.email' => ' ', + '$client.number' => ' ', + '$company.phone' => ' ', + '$company.state' => ' ', + '$credit.number' => '0029', + '$entity_number' => '0029', + '$credit_number' => '0029', + '$global_margin' => '6.35mm', + '$contact.phone' => '681-480-9828', + '$portal_button' => 'View client portal', + '$paymentButton' => 'Pay Now', + '$entity_footer' => 'Default invoice footer', + '$client.lang_2' => 'en', + '$product.date' => '', + '$client.email' => '', + '$product.item' => '', + '$public_notes' => '', + '$task.service' => '', + '$credit.total' => '$0.00', + '$net_subtotal' => '$0.00', + '$paid_to_date' => '$0.00', + '$quote.amount' => '$0.00', + '$company.city' => ' ', + '$payment.date' => ' ', + '$client.phone' => ' ', + '$number_short' => '0029', + '$quote.number' => '0029', + '$invoice.date' => '25/Feb/2023', + '$company.name' => '434343', + '$portalButton' => 'View client portal', + '$contact.name' => 'Benedict Eichmann', + '$entity.terms' => 'Default company invoice terms', + '$client.state' => 'North Carolina', + '$company.logo' => 'data:image/png;base64, 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', + '$company_logo' => 'data:image/png;base64, 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', + '$payment_link' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$status_logo' => '', + '$description' => '', + '$product.tax' => '', + '$valid_until' => '', + '$your_entity' => '', + '$balance_due' => '$0.00', + '$outstanding' => '$0.00', + '$partial_due' => '$0.00', + '$quote.total' => '$0.00', + '$payment_due' => ' ', + '$credit.date' => '25/Feb/2023', + '$invoiceDate' => '25/Feb/2023', + '$view_button' => 'View Invoice', + '$client.city' => 'Aufderharchester', + '$spc_qr_code' => 'SPC +0200 +1 + +K +434343 + + + + +CH + + + + + + + +0.000000 +USD + + + + + + + +NON + +0029 +EPD +', + '$client_name' => 'cypress', + '$client.name' => 'cypress', + '$paymentLink' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$payment_url' => 'http://ninja.test:8000/client/pay/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$page_layout' => 'portrait', + '$task.task1' => '', + '$task.task2' => '', + '$task.task3' => '', + '$task.task4' => '', + '$task.hours' => '', + '$amount_due' => '$0.00', + '$amount_raw' => '0.00', + '$invoice_no' => '0029', + '$quote.date' => '25/Feb/2023', + '$vat_number' => '975977515', + '$viewButton' => 'View Invoice', + '$portal_url' => 'http://ninja.test:8000/client/', + '$task.date' => '', + '$task.rate' => '', + '$task.cost' => '', + '$statement' => '', + '$user_iban' => ' ', + '$signature' => ' ', + '$id_number' => ' ', + '$credit_no' => '0029', + '$font_size' => '16px', + '$view_link' => 'View Invoice', + '$page_size' => 'A4', + '$country_2' => 'AF', + '$firstName' => 'Benedict', + '$user.name' => 'Derrick Monahan DDS Erna Wunsch', + '$font_name' => 'Roboto', + '$auto_bill' => 'This invoice will automatically be billed to your credit card on file on the due date.', + '$payments' => '', + '$task.tax' => '', + '$discount' => '$0.00', + '$subtotal' => '$0.00', + '$company1' => ' ', + '$company2' => ' ', + '$company3' => ' ', + '$company4' => ' ', + '$due_date' => ' ', + '$poNumber' => ' ', + '$quote_no' => '0029', + '$address2' => '63993 Aiyana View', + '$address1' => '8447', + '$viewLink' => 'View Invoice', + '$autoBill' => 'This invoice will automatically be billed to your credit card on file on the due date.', + '$view_url' => 'http://ninja.test:8000/client/invoice/UAUY8vIPuno72igmXbbpldwo5BDDKIqs', + '$font_url' => 'https://fonts.googleapis.com/css2?family=Roboto&display=swap', + '$details' => '', + '$balance' => '$0.00', + '$partial' => '$0.00', + '$client1' => ' ', + '$client2' => ' ', + '$client3' => ' ', + '$client4' => ' ', + '$dueDate' => ' ', + '$invoice' => '0029', + '$account' => '434343', + '$country' => 'Afghanistan', + '$contact' => 'Benedict Eichmann', + '$app_url' => 'http://ninja.test:8000', + '$website' => 'http://www.parisian.org/', + '$entity' => '', + '$thanks' => '', + '$amount' => '$0.00', + '$method' => ' ', + '$number' => '0029', + '$footer' => 'Default invoice footer', + '$client' => 'cypress', + '$email' => '', + '$notes' => '', + '_rate1' => '', + '_rate2' => '', + '_rate3' => '', + '$taxes' => '$0.00', + '$total' => '$0.00', + '$phone' => ' ', + '$terms' => 'Default company invoice terms', + '$from' => '', + '$item' => '', + '$date' => '25/Feb/2023', + '$tax' => '', + '$dir' => 'ltr', + '$to' => '', + ], + 'labels' => + [ + '$client.shipping_postal_code_label' => 'Shipping Postal Code', + '$client.billing_postal_code_label' => 'Postal Code', + '$company.city_state_postal_label' => 'City/State/Postal', + '$company.postal_city_state_label' => 'Postal/City/State', + '$product.gross_line_total_label' => 'Gross line total', + '$client.postal_city_state_label' => 'Postal/City/State', + '$client.shipping_address1_label' => 'Shipping Street', + '$client.shipping_address2_label' => 'Shipping Apt/Suite', + '$client.city_state_postal_label' => 'City/State/Postal', + '$client.shipping_address_label' => 'Shipping Address', + '$client.billing_address2_label' => 'Apt/Suite', + '$client.billing_address1_label' => 'Street', + '$client.shipping_country_label' => 'Shipping Country', + '$invoiceninja.whitelabel_label' => '', + '$client.billing_address_label' => 'Address', + '$client.billing_country_label' => 'Country', + '$task.gross_line_total_label' => 'Gross line total', + '$contact.portal_button_label' => 'view_client_portal', + '$client.shipping_state_label' => 'Shipping State/Province', + '$invoice.public_notes_label' => 'Public Notes', + '$client.shipping_city_label' => 'Shipping City', + '$client.billing_state_label' => 'State/Province', + '$product.description_label' => 'Description', + '$product.product_key_label' => 'Product', + '$entity.public_notes_label' => 'Public Notes', + '$invoice.balance_due_label' => 'Balance Due', + '$client.public_notes_label' => 'Notes', + '$company.postal_code_label' => 'Postal Code', + '$client.billing_city_label' => 'City', + '$secondary_font_name_label' => '', + '$product.line_total_label' => 'Line Total', + '$product.tax_amount_label' => 'Tax', + '$company.vat_number_label' => 'VAT Number', + '$invoice.invoice_no_label' => 'Invoice Number', + '$quote.quote_number_label' => 'Quote Number', + '$client.postal_code_label' => 'Postal Code', + '$contact.first_name_label' => 'First Name', + '$secondary_font_url_label' => '', + '$contact.signature_label' => '', + '$product.tax_name1_label' => 'Tax', + '$product.tax_name2_label' => 'Tax', + '$product.tax_name3_label' => 'Tax', + '$product.unit_cost_label' => 'Unit Cost', + '$quote.valid_until_label' => 'Valid Until', + '$custom_surcharge1_label' => '', + '$custom_surcharge2_label' => '', + '$custom_surcharge3_label' => '', + '$custom_surcharge4_label' => '', + '$quote.balance_due_label' => 'Balance Due', + '$company.id_number_label' => 'ID Number', + '$invoice.po_number_label' => 'PO Number', + '$invoice_total_raw_label' => 'Invoice Total', + '$postal_city_state_label' => 'Postal/City/State', + '$client.vat_number_label' => 'VAT Number', + '$city_state_postal_label' => 'City/State/Postal', + '$contact.full_name_label' => 'Name', + '$contact.last_name_label' => 'Last Name', + '$company.country_2_label' => 'Country', + '$product.product1_label' => '', + '$product.product2_label' => '', + '$product.product3_label' => '', + '$product.product4_label' => '', + '$statement_amount_label' => 'Amount', + '$task.description_label' => 'Description', + '$product.discount_label' => 'Discount', + '$entity_issued_to_label' => 'Invoice issued to', + '$assigned_to_user_label' => 'Name', + '$product.quantity_label' => 'Quantity', + '$total_tax_labels_label' => 'Taxes', + '$total_tax_values_label' => 'Taxes', + '$invoice.discount_label' => 'Discount', + '$invoice.subtotal_label' => 'Subtotal', + '$company.address2_label' => 'Apt/Suite', + '$partial_due_date_label' => 'Due Date', + '$invoice.due_date_label' => 'Due Date', + '$client.id_number_label' => 'ID Number', + '$credit.po_number_label' => 'PO Number', + '$company.address1_label' => 'Street', + '$credit.credit_no_label' => 'Invoice Number', + '$invoice.datetime_label' => 'Date', + '$contact.custom1_label' => '', + '$contact.custom2_label' => '', + '$contact.custom3_label' => '', + '$contact.custom4_label' => '', + '$task.line_total_label' => 'Line Total', + '$line_tax_labels_label' => 'Taxes', + '$line_tax_values_label' => 'Taxes', + '$secondary_color_label' => '', + '$invoice.balance_label' => 'Balance', + '$invoice.custom1_label' => '', + '$invoice.custom2_label' => '', + '$invoice.custom3_label' => '', + '$invoice.custom4_label' => '', + '$company.custom1_label' => '', + '$company.custom2_label' => '', + '$company.custom3_label' => '', + '$company.custom4_label' => '', + '$quote.po_number_label' => 'PO Number', + '$company.website_label' => 'Website', + '$balance_due_raw_label' => 'Balance Due', + '$entity.datetime_label' => 'Date', + '$credit.datetime_label' => 'Date', + '$client.address2_label' => 'Apt/Suite', + '$client.address1_label' => 'Street', + '$user.first_name_label' => 'First Name', + '$created_by_user_label' => 'Name', + '$client.currency_label' => '', + '$company.country_label' => 'Country', + '$company.address_label' => 'Address', + '$tech_hero_image_label' => '', + '$task.tax_name1_label' => 'Tax', + '$task.tax_name2_label' => 'Tax', + '$task.tax_name3_label' => 'Tax', + '$client.balance_label' => 'Account balance', + '$client_balance_label' => 'Account balance', + '$credit.balance_label' => 'Balance', + '$credit_balance_label' => 'Credit Balance', + '$gross_subtotal_label' => 'Subtotal', + '$invoice.amount_label' => 'Total', + '$client.custom1_label' => '', + '$client.custom2_label' => '', + '$client.custom3_label' => '', + '$client.custom4_label' => '', + '$emailSignature_label' => '', + '$invoice.number_label' => 'Invoice Number', + '$quote.quote_no_label' => 'Quote Number', + '$quote.datetime_label' => 'Date', + '$client_address_label' => 'Address', + '$client.address_label' => 'Address', + '$payment_button_label' => 'Pay Now', + '$payment_qrcode_label' => 'Pay Now', + '$client.country_label' => 'Country', + '$user.last_name_label' => 'Last Name', + '$client.website_label' => 'Website', + '$dir_text_align_label' => '', + '$entity_images_label' => '', + '$task.discount_label' => 'Discount', + '$contact.email_label' => 'Email', + '$primary_color_label' => '', + '$credit_amount_label' => 'Credit Amount', + '$invoice.total_label' => 'Invoice Total', + '$invoice.taxes_label' => 'Taxes', + '$quote.custom1_label' => '', + '$quote.custom2_label' => '', + '$quote.custom3_label' => '', + '$quote.custom4_label' => '', + '$company.email_label' => 'Email', + '$client.number_label' => 'Number', + '$company.phone_label' => 'Phone', + '$company.state_label' => 'State/Province', + '$credit.number_label' => 'Credit Number', + '$entity_number_label' => 'Invoice Number', + '$credit_number_label' => 'Invoice Number', + '$global_margin_label' => '', + '$contact.phone_label' => 'Phone', + '$portal_button_label' => 'view_client_portal', + '$paymentButton_label' => 'Pay Now', + '$entity_footer_label' => '', + '$client.lang_2_label' => '', + '$product.date_label' => 'Date', + '$client.email_label' => 'Email', + '$product.item_label' => 'Item', + '$public_notes_label' => 'Public Notes', + '$task.service_label' => 'Service', + '$credit.total_label' => 'Credit Total', + '$net_subtotal_label' => 'Net', + '$paid_to_date_label' => 'Paid to Date', + '$quote.amount_label' => 'Quote Total', + '$company.city_label' => 'City', + '$payment.date_label' => 'Payment Date', + '$client.phone_label' => 'Phone', + '$number_short_label' => 'Invoice #', + '$quote.number_label' => 'Quote Number', + '$invoice.date_label' => 'Invoice Date', + '$company.name_label' => 'Company Name', + '$portalButton_label' => 'view_client_portal', + '$contact.name_label' => 'Contact Name', + '$entity.terms_label' => 'Invoice Terms', + '$client.state_label' => 'State/Province', + '$company.logo_label' => 'Logo', + '$company_logo_label' => 'Logo', + '$payment_link_label' => 'Pay Now', + '$status_logo_label' => '', + '$description_label' => 'Description', + '$product.tax_label' => 'Tax', + '$valid_until_label' => 'Valid Until', + '$your_entity_label' => 'Your Invoice', + '$balance_due_label' => 'Balance Due', + '$outstanding_label' => 'Balance Due', + '$partial_due_label' => 'Partial Due', + '$quote.total_label' => 'Total', + '$payment_due_label' => 'Payment due', + '$credit.date_label' => 'Credit Date', + '$invoiceDate_label' => 'Invoice Date', + '$view_button_label' => 'View Invoice', + '$client.city_label' => 'City', + '$spc_qr_code_label' => '', + '$client_name_label' => 'Client Name', + '$client.name_label' => 'Client Name', + '$paymentLink_label' => 'Pay Now', + '$payment_url_label' => 'Pay Now', + '$page_layout_label' => '', + '$task.task1_label' => '', + '$task.task2_label' => '', + '$task.task3_label' => '', + '$task.task4_label' => '', + '$task.hours_label' => 'Hours', + '$amount_due_label' => 'Amount due', + '$amount_raw_label' => 'Amount', + '$invoice_no_label' => 'Invoice Number', + '$quote.date_label' => 'Quote Date', + '$vat_number_label' => 'VAT Number', + '$viewButton_label' => 'View Invoice', + '$portal_url_label' => '', + '$task.date_label' => 'Date', + '$task.rate_label' => 'Rate', + '$task.cost_label' => 'Rate', + '$statement_label' => 'Statement', + '$user_iban_label' => '', + '$signature_label' => '', + '$id_number_label' => 'ID Number', + '$credit_no_label' => 'Invoice Number', + '$font_size_label' => '', + '$view_link_label' => 'View Invoice', + '$page_size_label' => '', + '$country_2_label' => 'Country', + '$firstName_label' => 'First Name', + '$user.name_label' => 'Name', + '$font_name_label' => '', + '$auto_bill_label' => '', + '$payments_label' => 'Payments', + '$task.tax_label' => 'Tax', + '$discount_label' => 'Discount', + '$subtotal_label' => 'Subtotal', + '$company1_label' => '', + '$company2_label' => '', + '$company3_label' => '', + '$company4_label' => '', + '$due_date_label' => 'Due Date', + '$poNumber_label' => 'PO Number', + '$quote_no_label' => 'Quote Number', + '$address2_label' => 'Apt/Suite', + '$address1_label' => 'Street', + '$viewLink_label' => 'View Invoice', + '$autoBill_label' => '', + '$view_url_label' => 'View Invoice', + '$font_url_label' => '', + '$details_label' => 'Details', + '$balance_label' => 'Balance', + '$partial_label' => 'Partial Due', + '$client1_label' => '', + '$client2_label' => '', + '$client3_label' => '', + '$client4_label' => '', + '$dueDate_label' => 'Due Date', + '$invoice_label' => 'Invoice Number', + '$account_label' => 'Company Name', + '$country_label' => 'Country', + '$contact_label' => 'Name', + '$app_url_label' => '', + '$website_label' => 'Website', + '$entity_label' => 'Invoice', + '$thanks_label' => 'Thanks', + '$amount_label' => 'Total', + '$method_label' => 'Method', + '$number_label' => 'Invoice Number', + '$footer_label' => '', + '$client_label' => 'Client Name', + '$email_label' => 'Email', + '$notes_label' => 'Public Notes', + '_rate1_label' => 'Tax', + '_rate2_label' => 'Tax', + '_rate3_label' => 'Tax', + '$taxes_label' => 'Taxes', + '$total_label' => 'Total', + '$phone_label' => 'Phone', + '$terms_label' => 'Invoice Terms', + '$from_label' => 'From', + '$item_label' => 'Item', + '$date_label' => 'Invoice Date', + '$tax_label' => 'Tax', + '$dir_label' => '', + '$to_label' => 'To', + ], +]; + } +} diff --git a/app/Services/Pdf/PdfService.php b/app/Services/Pdf/PdfService.php new file mode 100644 index 000000000000..bb4b098dd39b --- /dev/null +++ b/app/Services/Pdf/PdfService.php @@ -0,0 +1,157 @@ +invitation = $invitation; + + $this->company = $invitation->company; + + $this->account = $this->company->account; + + $this->document_type = $document_type; + + $this->options = $options; + + } + + /** + * Resolves the PDF generation type and + * attempts to generate a PDF from the HTML + * string. + * + * @return mixed | Exception + * + */ + public function getPdf() + { + try { + + $pdf = $this->resolvePdfEngine(); + + $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; + } + + /** + * Renders the dom document to HTML + * + * @return string + * + */ + public function getHtml(): string + { + $html = $this->builder->getCompiledHTML(); + + if (config('ninja.log_pdf_html')) { + info($html); + } + + return $html; + } + + /** + * Initialize all the services to build the PDF + * + * @return self + */ + public function init(): self + { + + $this->config = (new PdfConfiguration($this))->init(); + + + $this->html_variables = $this->config->client ? + (new HtmlEngine($this->invitation))->generateLabelsAndValues() : + (new VendorHtmlEngine($this->invitation))->generateLabelsAndValues(); + + $this->designer = (new PdfDesigner($this))->build(); + + $this->builder = (new PdfBuilder($this))->build(); + + return $this; + + } + + /** + * resolvePdfEngine + * + * @return mixed + */ + private function resolvePdfEngine(): mixed + { + + 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()); + } + + return $pdf; + } + +} diff --git a/app/Services/PdfMaker/Design.php b/app/Services/PdfMaker/Design.php index b8a2da23707d..a633cfb7a902 100644 --- a/app/Services/PdfMaker/Design.php +++ b/app/Services/PdfMaker/Design.php @@ -175,10 +175,6 @@ class Design extends BaseDesign $this->sharedFooterElements(), ], ], - // 'swiss-qr' => [ - // 'id' => 'swiss-qr', - // 'elements' => $this->swissQrCodeElement(), - // ] ]; } diff --git a/app/Utils/Helpers.php b/app/Utils/Helpers.php index 5bef865ce085..c3c9ad8a10ba 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 */ diff --git a/database/factories/VendorContactFactory.php b/database/factories/VendorContactFactory.php index 03b8556e8b74..9f26356f2213 100644 --- a/database/factories/VendorContactFactory.php +++ b/database/factories/VendorContactFactory.php @@ -26,7 +26,12 @@ class VendorContactFactory extends Factory 'first_name' => $this->faker->firstName(), 'last_name' => $this->faker->lastName(), 'phone' => $this->faker->phoneNumber(), + 'email_verified_at' => now(), 'email' => $this->faker->unique()->safeEmail(), + 'send_email' => true, + 'password' => bcrypt('password'), + 'remember_token' => \Illuminate\Support\Str::random(10), + 'contact_key' => \Illuminate\Support\Str::random(32), ]; } } 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 { diff --git a/tests/Pdf/PdfServiceTest.php b/tests/Pdf/PdfServiceTest.php new file mode 100644 index 000000000000..d892bcc4c779 --- /dev/null +++ b/tests/Pdf/PdfServiceTest.php @@ -0,0 +1,111 @@ +makeTestData(); + } + + public function testPdfGeneration() + { + + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertNotNull($service->getPdf()); + + } + + public function testHtmlGeneration() + { + + $invitation = $this->invoice->invitations->first(); + + $service = new PdfService($invitation); + + $this->assertIsString($service->getHtml()); + + } + + 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); + + } + + 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 diff --git a/tests/Pdf/PdfmockTest.php b/tests/Pdf/PdfmockTest.php new file mode 100644 index 000000000000..ee66688657f0 --- /dev/null +++ b/tests/Pdf/PdfmockTest.php @@ -0,0 +1,99 @@ +build(); + + $this->assertInstanceOf(Invoice::class, $entity); + $this->assertNotNull($entity->client); + + + $pdf_service = new PdfService($entity->invitation); + + $this->assertNotNull($pdf_service); + + $pdf_config = (new PdfConfiguration($pdf_service)); + + $this->assertNotNull($pdf_config); + + + } + + public function testHtmlGeneration() + { + $pdf_mock = (new PdfMock()); + $mock = $pdf_mock->build(); + + $pdf_service = new PdfService($mock->invitation); + + $pdf_config = (new PdfConfiguration($pdf_service)); + $pdf_config->entity = $mock; + $pdf_config->setTaxMap($mock->tax_map); + $pdf_config->setTotalTaxMap($mock->total_tax_map); + $pdf_config->setCurrency(Currency::find(1)); + $pdf_config->setCountry(Country::find(840)); + $pdf_config->client = $mock->client; + $pdf_config->entity_design_id = 'invoice_design_id'; + $pdf_config->settings_object = $mock->client; + $pdf_config->entity_string = 'invoice'; + $pdf_config->settings = (object)$pdf_config->service->company->settings; + $pdf_config->setPdfVariables(); + $pdf_config->design = Design::find(2); + $pdf_config->currency_entity = $mock->client; + + $pdf_service->config = $pdf_config; + + $pdf_designer = (new \App\Services\Pdf\PdfDesigner($pdf_service))->build(); + $pdf_service->designer = $pdf_designer; + + $pdf_service->html_variables = $pdf_mock->getStubVariables(); + + $pdf_builder = (new PdfBuilder($pdf_service))->build(); + $pdf_service->builder = $pdf_builder; + $this->assertNotNull($pdf_config); + + $html = $pdf_service->getHtml(); + + nlog($html); + $this->assertNotNull($html); + } + +}