getFooterText() : ''; if ('' === trim($text)) { $text = 'quote' === $type ? \EasyInvoice\Controllers\SettingsController::getQuoteFooterText() : \EasyInvoice\Controllers\SettingsController::getInvoiceFooterText(); } /** * Filter the footer of a rendered document (page, PDF, email attachment). * * @param string $text Footer text or HTML. * @param object $document The document. * @param string $type 'invoice' or 'quote'. */ return (string) apply_filters('easy_invoice_pdf_footer_html', $text, $document, $type); } /** * Is this document a credit note? Credit notes are stored as their own * post type but render through the invoice designs. * * @param object $document Invoice model. */ public static function isCreditNote($document): bool { return is_object($document) && is_callable([$document, 'getId']) && get_post_type((int) $document->getId()) === \EasyInvoice\Constants\PostTypes::EASY_INVOICE_CREDIT_NOTE_POST_TYPE; } /** * The labels a design should use for this particular document: the * invoice labels as saved, or the credit-note wording when the document * is a credit note (a credit note is not a bill — no due date, no amount * to pay, no payment terms). * * @param array $text_settings From getInvoiceTextSettings(). * @param object $document Invoice model. * @return array */ public static function forDocument(array $text_settings, $document): array { if (!self::isCreditNote($document)) { return $text_settings; } $text_settings['invoice'] = __('Credit Note', 'easy-invoice'); $text_settings['invoice_number'] = __('Credit Note Number', 'easy-invoice'); $text_settings['invoice_date'] = __('Credit Note Date', 'easy-invoice'); $text_settings['total'] = __('Total Credited', 'easy-invoice'); $text_settings['total_due'] = __('Total Credited', 'easy-invoice'); return $text_settings; } /** * The document kind printed above the title. * * @param object $document Invoice model. */ public static function documentKind($document): string { return self::isCreditNote($document) ? __('Credit Note', 'easy-invoice') : \EasyInvoice\Controllers\SettingsController::getTextInvoice(); } /** * Get all text settings for templates * * @return array Array of text settings */ public static function getAllTextSettings(): array { return [ // Invoice specific 'invoice_number' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceNumber(), 'invoice_date' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceDate(), 'due_date' => \EasyInvoice\Controllers\SettingsController::getTextDueDate(), 'total_due' => \EasyInvoice\Controllers\SettingsController::getTextTotalDue(), // Quote specific 'quote_number' => \EasyInvoice\Controllers\SettingsController::getTextQuoteNumber(), 'quote_date' => \EasyInvoice\Controllers\SettingsController::getTextQuoteDate(), 'valid_until' => \EasyInvoice\Controllers\SettingsController::getTextValidUntil(), // Common labels 'to' => \EasyInvoice\Controllers\SettingsController::getTextTo(), 'service' => \EasyInvoice\Controllers\SettingsController::getTextService(), 'qty' => \EasyInvoice\Controllers\SettingsController::getTextQty(), 'rate_price' => \EasyInvoice\Controllers\SettingsController::getTextRatePrice(), 'adjust' => \EasyInvoice\Controllers\SettingsController::getTextAdjust(), 'sub_total' => \EasyInvoice\Controllers\SettingsController::getTextSubTotal(), 'total' => \EasyInvoice\Controllers\SettingsController::getTextTotal(), 'line_total' => \EasyInvoice\Controllers\SettingsController::getTextLineTotal(), 'tax' => \EasyInvoice\Controllers\SettingsController::getTextTax(), 'discount' => \EasyInvoice\Controllers\SettingsController::getTextDiscount(), // Button labels 'print' => \EasyInvoice\Controllers\SettingsController::getTextPrint(), 'download_pdf' => \EasyInvoice\Controllers\SettingsController::getTextDownloadPdf(), 'send_email' => \EasyInvoice\Controllers\SettingsController::getTextSendEmail(), 'pay_now' => \EasyInvoice\Controllers\SettingsController::getTextPayNow(), 'accept_quote' => \EasyInvoice\Controllers\SettingsController::getTextAcceptQuote(), 'decline_quote' => \EasyInvoice\Controllers\SettingsController::getTextDeclineQuote(), 'decline_reason' => \EasyInvoice\Controllers\SettingsController::getTextDeclineReason(), ]; } /** * Get company information from settings * * @return array Company information */ public static function getCompanyInfo(): array { $info = [ 'name' => get_option('easy_invoice_company_name', 'Your Company Name'), 'address' => get_option('easy_invoice_company_address', ''), 'email' => get_option('easy_invoice_company_email', ''), 'phone' => get_option('easy_invoice_company_phone', ''), 'website' => get_option('easy_invoice_company_website', ''), 'logo' => get_option('easy_invoice_company_logo', ''), 'tax_number' => get_option('easy_invoice_tax_number', '') ?: get_option('easy_invoice_company_vat_number', ''), ]; /** * Filter the resolved company info used by invoice/quote templates * and PDF generation. The White-Label addon hooks this to replace * the logo with its branded header image without forcing every * admin to also see the white-label logo on their settings page. * * @param array $info Resolved company info. */ return apply_filters('easy_invoice_template_company_info', $info); } /** * Get invoice specific text settings * * @return array Invoice text settings */ public static function getInvoiceTextSettings(): array { return [ 'invoice_number' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceNumber(), 'invoice_date' => \EasyInvoice\Controllers\SettingsController::getTextInvoiceDate(), 'due_date' => \EasyInvoice\Controllers\SettingsController::getTextDueDate(), 'total_due' => \EasyInvoice\Controllers\SettingsController::getTextTotalDue(), 'to' => \EasyInvoice\Controllers\SettingsController::getTextTo(), 'service' => \EasyInvoice\Controllers\SettingsController::getTextService(), 'qty' => \EasyInvoice\Controllers\SettingsController::getTextQty(), 'rate_price' => \EasyInvoice\Controllers\SettingsController::getTextRatePrice(), 'adjust' => \EasyInvoice\Controllers\SettingsController::getTextAdjust(), 'sub_total' => \EasyInvoice\Controllers\SettingsController::getTextSubTotal(), 'total' => \EasyInvoice\Controllers\SettingsController::getTextTotal(), 'line_total' => \EasyInvoice\Controllers\SettingsController::getTextLineTotal(), 'tax' => \EasyInvoice\Controllers\SettingsController::getTextTax(), 'discount' => \EasyInvoice\Controllers\SettingsController::getTextDiscount(), 'print' => \EasyInvoice\Controllers\SettingsController::getTextPrint(), 'download_pdf' => \EasyInvoice\Controllers\SettingsController::getTextDownloadPdf(), 'send_email' => \EasyInvoice\Controllers\SettingsController::getTextSendEmail(), 'pay_now' => \EasyInvoice\Controllers\SettingsController::getTextPayNow(), ]; } /** * Get quote specific text settings * * @return array Quote text settings */ public static function getQuoteTextSettings(): array { return [ 'quote_number' => \EasyInvoice\Controllers\SettingsController::getTextQuoteNumber(), 'quote_date' => \EasyInvoice\Controllers\SettingsController::getTextQuoteDate(), 'valid_until' => \EasyInvoice\Controllers\SettingsController::getTextValidUntil(), 'to' => \EasyInvoice\Controllers\SettingsController::getTextTo(), 'service' => \EasyInvoice\Controllers\SettingsController::getTextService(), 'qty' => \EasyInvoice\Controllers\SettingsController::getTextQty(), 'rate_price' => \EasyInvoice\Controllers\SettingsController::getTextRatePrice(), 'adjust' => \EasyInvoice\Controllers\SettingsController::getTextAdjust(), 'sub_total' => \EasyInvoice\Controllers\SettingsController::getTextSubTotal(), 'total' => \EasyInvoice\Controllers\SettingsController::getTextTotal(), 'line_total' => \EasyInvoice\Controllers\SettingsController::getTextLineTotal(), 'tax' => \EasyInvoice\Controllers\SettingsController::getTextTax(), 'discount' => \EasyInvoice\Controllers\SettingsController::getTextDiscount(), 'print' => \EasyInvoice\Controllers\SettingsController::getTextPrint(), 'download_pdf' => \EasyInvoice\Controllers\SettingsController::getTextDownloadPdf(), 'send_email' => \EasyInvoice\Controllers\SettingsController::getTextSendEmail(), 'accept_quote' => \EasyInvoice\Controllers\SettingsController::getTextAcceptQuote(), 'decline_quote' => \EasyInvoice\Controllers\SettingsController::getTextDeclineQuote(), 'decline_reason' => \EasyInvoice\Controllers\SettingsController::getTextDeclineReason(), ]; } /** * Generate "From" section content for templates * * @param array $company_info Company information array * @param bool $show_label Whether to show the "From" label * @param string $style Style variant for the "From" section * @return string Generated HTML for "From" section */ public static function generateFromSection($company_info, $show_label = true, $style = 'default'): string { $output = ''; switch ($style) { case 'minimal': // Minimal style - includes company name and details $output .= '
'; $output .= '
' . esc_html($company_info['name']) . '
'; if ($company_info['address']) { $output .= '
' . nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= '
' . esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= '
' . esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= '
' . esc_html($company_info['website']) . '
'; } $output .= '
'; break; case 'details-only': // Details only style - excludes company name (for use with separate company-name div) $output .= '
'; if ($company_info['address']) { $output .= '
' . nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= '
' . esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= '
' . esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= '
' . esc_html($company_info['website']) . '
'; } if (!empty($company_info['tax_number'])) { // A tax identifier belongs on the document itself; EU B2B // invoices are not valid without the supplier's VAT ID. $output .= '
' . esc_html(self::taxIdLabel()) . ' ' . esc_html($company_info['tax_number']) . '
'; } $output .= '
'; break; case 'elegant': // Elegant style - subtle design $output .= '
'; if ($show_label) { $output .= '

' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . '

'; } $output .= '
'; $output .= '
' . esc_html($company_info['name']) . '
'; if ($company_info['address']) { $output .= '
' . nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= '
' . esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= '
' . esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= '
' . esc_html($company_info['website']) . '
'; } $output .= '
'; $output .= '
'; break; case 'modern': // Modern style - clean and bold $output .= '
'; if ($show_label) { $output .= '

' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . ':

'; } $output .= '
'; $output .= '
' . esc_html($company_info['name']) . '
'; if ($company_info['address']) { $output .= nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= esc_html($company_info['website']); } $output .= '
'; $output .= '
'; break; case 'professional': // Professional style - structured layout $output .= '
'; if ($show_label) { $output .= '

' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . ':

'; } $output .= '
'; $output .= '
' . esc_html($company_info['name']) . '
'; if ($company_info['address']) { $output .= nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= esc_html($company_info['website']); } $output .= '
'; $output .= '
'; break; case 'creative': // Creative style - artistic design $output .= '
'; if ($show_label) { $output .= '

' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . '

'; } $output .= '
'; $output .= '
' . esc_html($company_info['name']) . '
'; if ($company_info['address']) { $output .= nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= esc_html($company_info['website']); } $output .= '
'; $output .= '
'; break; default: // Default style - standard layout $output .= '
'; if ($show_label) { $output .= '

' . esc_html(\EasyInvoice\Controllers\SettingsController::getTextFrom()) . ':

'; } $output .= '
'; $output .= '
' . esc_html($company_info['name']) . '
'; if ($company_info['address']) { $output .= nl2br(esc_html($company_info['address'])) . '
'; } if ($company_info['email']) { $output .= esc_html($company_info['email']) . '
'; } if ($company_info['phone']) { $output .= esc_html($company_info['phone']) . '
'; } if ($company_info['website']) { $output .= esc_html($company_info['website']); } $output .= '
'; $output .= '
'; break; } return $output; } /** * What is still owed on an invoice: the total less completed payments. * * @param mixed $invoice Invoice model. * @return float */ public static function amountDue($invoice): float { // Total less completed payments and credit notes; see InvoiceBalance // for the `easy_invoice_amount_due` filter. return \EasyInvoice\Services\InvoiceBalance::due($invoice); } /** * Label in front of a tax identifier, on both sides of the document. * * @return string */ public static function taxIdLabel(): string { /** * Filter the label shown before tax identifiers on documents. * * @param string $label Label, "VAT" by default. */ return (string) apply_filters('easy_invoice_tax_id_label', __('VAT', 'easy-invoice')); } /** * The recipient block of a document: who it is for and how to reach them. * * Business name first, the contact person underneath when the client * record has both, then address, email, phone and the client's VAT * number. Returns '' when there is nobody to address, so a design can * leave the block out instead of printing an empty "To". * * @param mixed $document Invoice or Quote model. * @return string HTML, or '' when the document has no recipient. */ public static function generateClientBlock($document): string { if (!is_object($document)) { return ''; } $name = trim((string) (is_callable([$document, 'getCustomerName']) ? $document->getCustomerName() : '')); $address = trim((string) (is_callable([$document, 'getCustomerAddress']) ? $document->getCustomerAddress() : '')); $email = trim((string) (is_callable([$document, 'getCustomerEmail']) ? $document->getCustomerEmail() : '')); $vat = trim((string) (is_callable([$document, 'getCustomerVatNumber']) ? $document->getCustomerVatNumber() : '')); $contact = ''; $phone = ''; $client_id = is_callable([$document, 'getClientId']) ? (int) $document->getClientId() : 0; if ($client_id > 0) { $client = (new \EasyInvoice\Repositories\ClientRepository())->find($client_id); if ($client) { $person = trim((string) $client->getFirstName() . ' ' . (string) $client->getLastName()); if ('' !== $person && '' !== $name && 0 !== strcasecmp($person, $name)) { $contact = $person; } $phone = trim((string) $client->getPhone()); if ('' === $address) { $address = trim((string) $client->getAddress()); } if ('' === $email) { $email = trim((string) $client->getEmail()); } } } if ('' === $name . $address . $email . $phone . $vat) { return ''; } $output = ''; if ('' !== $name) { $output .= '
' . esc_html($name) . '
'; } if ('' !== $contact) { $output .= '
' . esc_html($contact) . '
'; } if ('' !== $address) { $output .= '
' . nl2br(esc_html($address)) . '
'; } if ('' !== $email) { $output .= '
' . esc_html($email) . '
'; } if ('' !== $phone) { $output .= '
' . esc_html($phone) . '
'; } if ('' !== $vat) { $output .= '
' . esc_html(self::taxIdLabel()) . ' ' . esc_html($vat) . '
'; } /** * Filter the recipient block rendered by every document design. * * @param string $output HTML lines. * @param mixed $document Invoice or Quote model. */ return (string) apply_filters('easy_invoice_client_block_html', $output, $document); } /** * Generate totals section for invoice/quote templates * * @param mixed $document Invoice or Quote object * @param array $text_settings Text settings array * @param mixed $formatter Formatter object * @param string $type 'invoice' or 'quote' * @return string Generated HTML for totals section */ public static function generateTotalsSection($document, array $text_settings, $formatter, string $type = 'invoice'): string { $output = ''; // Subtotal $output .= '
'; $output .= '' . esc_html($text_settings['sub_total']) . ''; $output .= '' . esc_html($formatter->format($document->getSubtotal())) . ''; $output .= '
'; $discount_row = ''; if ($document->getDiscountAmount() > 0) { $discount_row .= '
'; $discount_row .= '' . esc_html($text_settings['discount']); if ($document->getDiscountType() === 'percentage') { $discount_row .= ' (' . esc_html($document->getDiscountValue()) . '%)'; } $discount_row .= ''; $discount_row .= '-' . esc_html($formatter->format($document->getDiscountAmount())) . ''; $discount_row .= '
'; } $tax_row = ''; if ($document->getTaxAmount() > 0) { $tax_row .= '
'; $tax_row .= '' . esc_html(easy_invoice_get_tax_name()); $tax_row .= ' (' . esc_html($document->getTaxRate()) . '%)'; $tax_row .= '' . esc_html($formatter->format($document->getTaxAmount())) . ''; $tax_row .= '
'; } // The rows read in the order the figures are computed: a discount taken // before tax sits above the tax line; one taken off the taxed total sits // below it. Shown the other way round, the arithmetic does not add up // on the page. // The model treats anything but 'before_tax' as after-tax. $method = (string) ($document->discount_calculation_method ?? ''); $output .= ('before_tax' === $method) ? $discount_row . $tax_row : $tax_row . $discount_row; // Allow plugins to add content. Captured so it lands between the // rows and the grand total rather than printing ahead of the block. ob_start(); if ($type === 'invoice') { do_action('easy_invoice_invoice_totals_after_tax', $document); do_action('easy_invoice_invoice_totals_after_discount', $document); } else { do_action('easy_invoice_quote_totals_after_tax', $document); do_action('easy_invoice_quote_totals_after_discount', $document); } $output .= (string) ob_get_clean(); // Grand Total $output .= '
'; $output .= '' . esc_html($text_settings['total']) . ''; $output .= '' . esc_html($formatter->format($document->getTotal())) . ''; $output .= '
'; // Money already received: an invoice that is part paid must say what // is still owed, or the reader pays the total again. Skipped when an // addon has printed its own payment breakdown above. if ('invoice' === $type && false === strpos($output, 'partial-payments-breakdown') && is_callable([$document, 'getId'])) { $invoice_id = (int) $document->getId(); $paid = \EasyInvoice\Services\InvoiceBalance::paid($invoice_id); $credited = \EasyInvoice\Services\InvoiceBalance::credited($invoice_id); /** * Filter whether the totals block lists payments received, credit * notes and the balance due. * * @param bool $show Default true when something has been paid or credited. * @param object $document Invoice model. */ if (($paid > 0 || $credited > 0) && apply_filters('easy_invoice_totals_show_payments', true, $document)) { $balance = max(0, (float) $document->getTotal() - $paid - $credited); // A credit note corrects the invoice; the reader needs to see // it counted off before the balance. foreach (\EasyInvoice\Services\CreditNote::forInvoice($invoice_id) as $credit_id) { $credit_amount = (float) get_post_meta($credit_id, '_easy_invoice_total', true); if ($credit_amount <= 0) { continue; } $credit_number = (string) get_post_meta($credit_id, '_easy_invoice_number', true); $output .= '
'; /* translators: %s: credit note number. */ $output .= '' . esc_html('' !== $credit_number ? sprintf(__('Credit note %s', 'easy-invoice'), $credit_number) : __('Credit note', 'easy-invoice')) . ''; $output .= '-' . esc_html($formatter->format($credit_amount)) . ''; $output .= '
'; } if ($paid > 0) { $output .= '
'; $output .= '' . esc_html__('Paid', 'easy-invoice') . ''; $output .= '-' . esc_html($formatter->format($paid)) . ''; $output .= '
'; } $output .= '
'; $output .= '' . esc_html__('Balance due', 'easy-invoice') . ''; $output .= '' . esc_html($formatter->format($balance)) . ''; $output .= '
'; } } return $output; } }