PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
← All changes | includes/Controllers/SettingsController.php +222 -333 2.3.4 → 2.4.3 View file →
@@ -25,8 +25,25 @@
25 25 */
26 26 private $settings_cache = null;
27 27
28 28 /**
29 + * Settings resolved once per request. Every money formatter used to
30 + * construct its own controller and rebuild the whole settings array (the
31 + * config tree with every translated label) — around a millisecond per
32 + * formatted amount, ten seconds on a 4,500-line statement.
33 + *
34 + * @var array|null
35 + */
36 + private static $shared_settings_cache = null;
37 +
38 + /**
39 + * Drop the per-request settings cache (any easy_invoice_* option changed).
40 + */
41 + public static function flushSharedCache(): void {
42 + self::$shared_settings_cache = null;
43 + }
44 +
45 + /**
29 46 * Settings configuration cache
30 47 *
31 48 * @var array
32 49 */
@@ -58,8 +75,10 @@
58 75 add_action('wp_ajax_easy_invoice_test_email', [$this, 'testEmail']);
59 76 add_action('wp_ajax_easy_invoice_test_template_email', [$this, 'testTemplateEmail']);
60 77 add_action('wp_ajax_easy_invoice_test_payment_reminder_email', [$this, 'testPaymentReminderEmail']);
61 78 add_action('wp_ajax_regenerate_invoice_numbers', [$this, 'ajaxRegenerateInvoiceNumbers']);
79 + add_action('wp_ajax_easy_invoice_add_offline_method', [$this, 'ajaxAddOfflineMethod']);
80 + add_action('wp_ajax_easy_invoice_remove_offline_method', [$this, 'ajaxRemoveOfflineMethod']);
62 81 add_action('wp_ajax_regenerate_quote_numbers', [$this, 'ajaxRegenerateQuoteNumbers']);
63 82
64 83 // Clear cache on admin init to ensure new fields are recognized
65 84 add_action('admin_init', [$this, 'clearCache']);
@@ -181,8 +200,12 @@
181 200 'easy_invoice_company_name' => ['label' => __('Company Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
182 201 'easy_invoice_company_email' => ['label' => __('Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-6' ],
183 202 'easy_invoice_company_phone' => ['label' => __('Phone Number', 'easy-invoice'), 'type' => 'tel', 'default' => '', 'col_span' => 'sm:col-span-6' ],
184 203 'easy_invoice_company_website' => ['label' => __('Website', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6' ],
204 + // Both are required by EN 16931 and are what makes an intra-EU
205 + // reverse charge determinable. See Services\TaxTreatment.
206 + 'easy_invoice_company_vat_number' => ['label' => __('VAT number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3', 'description' => __('Your VAT identifier, including the country prefix — for example DE123456789.', 'easy-invoice') ],
207 + 'easy_invoice_company_country' => ['label' => __('Country', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3', 'description' => __('Two-letter country code, for example DE, FR or GB.', 'easy-invoice') ],
185 208 'easy_invoice_company_address' => ['label' => __('Address', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ],
186 209 'easy_invoice_tax_number' => ['label' => __('Tax ID / VAT Number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
187 210 'easy_invoice_company_logo' => ['label' => __('Company Logo', 'easy-invoice'), 'type' => 'image', 'default' => '', 'col_span' => 'sm:col-span-6' ],
188 211 ]
@@ -282,9 +305,9 @@
282 305 ],
283 306 'easy_invoice_quote_footer_text' => [
284 307 'label' => __('Footer Text', 'easy-invoice'),
285 308 'type' => 'wp_editor',
286 - 'default' => __('Thanks for choosing Easy Invoice', 'easy-invoice'),
309 + 'default' => __('Thank you for your business.', 'easy-invoice'),
287 310 'description' => __('You can modify your quote footer text from here. HTML tags supports: a, br, em, strong, hr, p, h1 to h4', 'easy-invoice'),
288 311 'col_span' => 'sm:col-span-6'
289 312 ],
290 313 'easy_invoice_quote_accept_button' => [
@@ -422,8 +445,15 @@
422 445 'easy_invoice_email_reply_to' => ['label' => __('Reply-To Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-3'],
423 446 'easy_invoice_email_reply_to_name' => ['label' => __('Reply-To Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3'],
424 447 'easy_invoice_enable_email_styling' => ['label' => __('Enable HTML Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
425 448 'easy_invoice_bcc_admin' => ['label' => __('BCC Admin on All Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6'],
449 + // Off by default: attaching a document changes what every
450 + // customer receives and makes messages much larger, which
451 + // some SMTP relays reject. See EmailManager::shouldAttachInvoicePdf().
452 + 'easy_invoice_attach_pdf_to_email' => ['label' => __('Attach a PDF copy to invoice and quote emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6', 'description' => __('Generates the document on the server and attaches it. The PDF contains real, selectable text rather than an image.', 'easy-invoice')],
453 + 'easy_invoice_pdf_paper_size' => ['label' => __('PDF paper size', 'easy-invoice'), 'type' => 'select', 'default' => 'a4', 'options' => ['a4' => 'A4', 'letter' => 'Letter', 'legal' => 'Legal'], 'col_span' => 'sm:col-span-3'],
454 + 'easy_invoice_pdf_download_method' => ['label' => __('"Download as PDF" button', 'easy-invoice'), 'type' => 'select', 'default' => 'browser', 'options' => ['browser' => __('Exactly as shown on screen (rendered in the browser)', 'easy-invoice'), 'server' => __('Generated on the server (real text, smaller file)', 'easy-invoice')], 'col_span' => 'sm:col-span-3', 'description' => __('The browser capture reproduces the page pixel for pixel — every design, custom template and watermark. The server file is selectable text and a fraction of the size. Emails, the API and e-invoicing always use the server.', 'easy-invoice')],
455 + 'easy_invoice_pdf_layout' => ['label' => __('Server-generated PDF layout', 'easy-invoice'), 'type' => 'select', 'default' => 'design', 'options' => ['design' => __('Match the selected invoice / quote design', 'easy-invoice'), 'plain' => __('Plain print layout', 'easy-invoice')], 'col_span' => 'sm:col-span-3', 'description' => __('Applies to PDFs the server produces: email attachments, the API, e-invoicing, and the download button when set to "server". The design is the template chosen on the document; the plain layout is a neutral table-based page.', 'easy-invoice')],
426 456 'easy_invoice_admin_email' => ['label' => __('Admin Email for BCC', 'easy-invoice'), 'type' => 'email', 'default' => get_option('admin_email', ''), 'col_span' => 'sm:col-span-6'],
427 457 'easy_invoice_email_logo' => ['label' => __('Email Logo URL', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6'],
428 458 'easy_invoice_email_footer_text' => ['label' => __('Email Footer Text', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6'],
429 459 'easy_invoice_test_email' => [
@@ -440,41 +470,9 @@
440 470 'description' => __('Configure email sent when an invoice is available for the client', 'easy-invoice'),
441 471 'fields' => [
442 472 'easy_invoice_invoice_email_enabled' => ['label' => __('Enable Invoice Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
443 473 'easy_invoice_invoice_email_subject' => ['label' => __('Subject', 'easy-invoice'), 'type' => 'text', 'default' => __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
444 - 'easy_invoice_invoice_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>📄 Your Invoice is Ready</h2>
445 -
446 -<p>Dear {{client_name}},</p>
447 -
448 -<div class="highlight-box">
449 - <p><strong>Invoice #{{invoice_number}}</strong><br>
450 - <span class="amount-highlight">{{total_amount}}</span><br>
451 - Due Date: <strong>{{due_date}}</strong></p>
452 -</div>
453 -
454 -<p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
455 -
456 -<div class="info-box">
457 - <p><strong>📋 Payment Details:</strong><br>
458 - • Invoice Number: {{invoice_number}}<br>
459 - • Total Amount: {{total_amount}}<br>
460 - • Due Date: {{due_date}}<br>
461 - • Payment Terms: {{payment_terms}}</p>
462 -</div>
463 -
464 -<div class="warning-box">
465 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
466 -</div>
467 -
468 -<p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
469 -
470 -<div class="divider"></div>
471 -
472 -<p>Thank you for your business!</p>
473 -
474 -<p>Best regards,<br>
475 -<strong>{{company_name}}</strong><br>
476 -{{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
474 + 'easy_invoice_invoice_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'), 'col_span' => 'sm:col-span-6'],
477 475 'easy_invoice_invoice_test_template' => [
478 476 'label' => __('Test Invoice Email', 'easy-invoice'),
479 477 'type' => 'test_template_email',
480 478 'default' => '',
@@ -489,46 +487,9 @@
489 487 'description' => __('Configure email sent when a quote is available for the client', 'easy-invoice'),
490 488 'fields' => [
491 489 'easy_invoice_quote_email_enabled' => ['label' => __('Enable Quote Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
492 490 'easy_invoice_quote_email_subject' => ['label' => __('Subject', 'easy-invoice'), 'type' => 'text', 'default' => __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
493 - 'easy_invoice_quote_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>📋 Your Quote is Ready</h2>
494 -
495 -<p>Dear {{client_name}},</p>
496 -
497 -<div class="highlight-box">
498 - <p><strong>Quote #{{quote_number}}</strong><br>
499 - <span class="amount-highlight">{{total_amount}}</span><br>
500 - Valid Until: <strong>{{expiry_date}}</strong></p>
501 -</div>
502 -
503 -<p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment or visit the link below.</p>
504 -
505 -<div class="info-box">
506 - <p><strong>📋 Quote Summary:</strong><br>
507 - • Quote Number: {{quote_number}}<br>
508 - • Total Amount: {{total_amount}}<br>
509 - • Valid Until: {{expiry_date}}<br>
510 - • Terms: {{payment_terms}}</p>
511 -</div>
512 -
513 -<div class="highlight-box">
514 - <p><strong>🔗 View Quote Online:</strong><br>
515 - <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
516 -</div>
517 -
518 -<div class="warning-box">
519 - <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
520 -</div>
521 -
522 -<p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
523 -
524 -<div class="divider"></div>
525 -
526 -<p>We look forward to working with you!</p>
527 -
528 -<p>Best regards,<br>
529 -<strong>{{company_name}}</strong><br>
530 -{{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
491 + 'easy_invoice_quote_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('quote'), 'col_span' => 'sm:col-span-6'],
531 492 'easy_invoice_quote_test_template' => [
532 493 'label' => __('Test Quote Email', 'easy-invoice'),
533 494 'type' => 'test_template_email',
534 495 'default' => '',
@@ -543,45 +504,9 @@
543 504 'description' => __('Configure email sent when a payment is received', 'easy-invoice'),
544 505 'fields' => [
545 506 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
546 507 'easy_invoice_payment_email_subject' => ['label' => __('Subject', 'easy-invoice'), 'type' => 'text', 'default' => __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
547 - 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>✅ Payment Received - Thank You!</h2>
548 -
549 -<p>Dear {{client_name}},</p>
550 -
551 -<div class="success-box">
552 - <p><strong>Payment Confirmation</strong><br>
553 - Invoice #{{invoice_number}}<br>
554 - <span class="amount-highlight">{{payment_amount}}</span><br>
555 - Payment Date: <strong>{{payment_date}}</strong><br>
556 - Payment Method: <strong>{{payment_method}}</strong></p>
557 -</div>
558 -
559 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
560 -
561 -<div class="info-box">
562 - <p><strong>📊 Payment Details:</strong><br>
563 - • Invoice Number: {{invoice_number}}<br>
564 - • Amount Paid: {{payment_amount}}<br>
565 - • Payment Date: {{payment_date}}<br>
566 - • Payment Method: {{payment_method}}<br>
567 - • Transaction ID: {{transaction_id}}</p>
568 -</div>
569 -
570 -<div class="highlight-box">
571 - <p><strong>🎉 Status: PAID</strong><br>
572 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
573 -</div>
574 -
575 -<p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
576 -
577 -<div class="divider"></div>
578 -
579 -<p>Thank you for choosing our services!</p>
580 -
581 -<p>Best regards,<br>
582 -<strong>{{company_name}}</strong><br>
583 -{{company_email}}</p>', 'easy-invoice'), 'col_span' => 'sm:col-span-6'],
508 + 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => \EasyInvoice\Services\EmailManager::defaultTemplate('payment'), 'col_span' => 'sm:col-span-6'],
584 509 'easy_invoice_payment_test_template' => [
585 510 'label' => __('Test Payment Email', 'easy-invoice'),
586 511 'type' => 'test_template_email',
587 512 'default' => '',
@@ -617,8 +542,9 @@
617 542 'easy_invoice_text_rate_price' => ['label' => __('Rate', 'easy-invoice'), 'type' => 'text', 'default' => __('Rate', 'easy-invoice'), 'col_span' => '', 'description' => __('Rate or price label', 'easy-invoice')],
618 543 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')],
619 544 'easy_invoice_text_sub_total' => ['label' => __('Sub Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Sub Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Sub total label', 'easy-invoice')],
620 545 'easy_invoice_text_total' => ['label' => __('Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Total label', 'easy-invoice')],
546 + 'easy_invoice_text_line_total' => ['label' => __('Line total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Heading of the line-item amount column', 'easy-invoice')],
621 547 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')],
622 548 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')],
623 549 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
624 550 'easy_invoice_text_download_pdf' => ['label' => __('Download as PDF', 'easy-invoice'), 'type' => 'text', 'default' => __('Download as PDF', 'easy-invoice'), 'col_span' => '', 'description' => __('Download PDF button text', 'easy-invoice')],
@@ -652,9 +578,10 @@
652 578 'col_span' => 'sm:col-span-3'
653 579 ],
654 580
655 581 'easy_invoice_invoice_numbering' => ['label' => __('Auto-increment invoice numbers', 'easy-invoice'), 'type' => 'checkbox', 'description' => __('Automatically increment invoice numbers for new invoices', 'easy-invoice'), 'default' => 'yes', 'col_span' => 'sm:col-span-6' ],
656 - 'easy_invoice_payment_reminder_days' => ['label' => __('Payment Reminder Days', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'col_span' => 'sm:col-span-3'],
582 + 'easy_invoice_payment_reminder_days' => ['label' => __('Payment reminder (days after due date)', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'min' => 0, 'col_span' => 'sm:col-span-3', 'description' => __('Emails the Reminder template once to each invoice still owed this many days after its due date. Leave empty to switch reminders off. Easy Invoice Pro replaces this with its own schedule.', 'easy-invoice')],
583 + 'easy_invoice_remove_data_on_uninstall' => ['label' => __('Remove all data when the plugin is deleted', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6', 'description' => __('Off, deleting the plugin from the Plugins screen keeps every invoice, quote, payment, client record and setting, so reinstalling picks up where you left off. On, all of it is removed — invoices are tax records, so leave this off unless you are certain.', 'easy-invoice')],
657 584 ]
658 585 ],
659 586 ];
660 587
@@ -891,22 +818,62 @@
891 818 }
892 819 }
893 820
894 821 /**
822 + * Settings → Payment Methods → "Add an offline method": registers a new
823 + * offline payment method (ACH, wire transfer, mobile money, …) by name.
824 + */
825 + public function ajaxAddOfflineMethod(): void {
826 + check_ajax_referer('easy_invoice_settings', 'nonce');
827 + if (!current_user_can('manage_options')) {
828 + wp_send_json_error(['message' => __('You do not have permission to change payment methods.', 'easy-invoice')]);
829 + }
830 + $title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
831 + $id = \EasyInvoice\Services\OfflinePayments::addCustom($title);
832 + if (is_wp_error($id)) {
833 + wp_send_json_error(['message' => $id->get_error_message()]);
834 + }
835 + wp_send_json_success([
836 + 'id' => $id,
837 + /* translators: %s: method name. */
838 + 'message' => sprintf(__('"%s" added. Fill in its instructions below and save.', 'easy-invoice'), $title),
839 + ]);
840 + }
841 +
842 + /**
843 + * Remove a merchant-added offline method. Recorded payments keep the method name.
844 + */
845 + public function ajaxRemoveOfflineMethod(): void {
846 + check_ajax_referer('easy_invoice_settings', 'nonce');
847 + if (!current_user_can('manage_options')) {
848 + wp_send_json_error(['message' => __('You do not have permission to change payment methods.', 'easy-invoice')]);
849 + }
850 + $id = isset($_POST['id']) ? sanitize_key(wp_unslash($_POST['id'])) : '';
851 + if ('' === $id || !\EasyInvoice\Services\OfflinePayments::removeCustom($id)) {
852 + wp_send_json_error(['message' => __('That payment method cannot be removed.', 'easy-invoice')]);
853 + }
854 + wp_send_json_success(['message' => __('Payment method removed.', 'easy-invoice')]);
855 + }
856 +
857 + /**
895 858 * Save settings
896 859 *
897 860 * @return void
898 861 */
899 862 public function saveSettings() {
900 - // Verify nonce
901 - if (!wp_verify_nonce($_POST['easy_invoice_settings_nonce'], 'easy_invoice_settings')) {
902 - wp_die(__('Security check failed', 'easy-invoice'));
863 + // Verify nonce. $_POST['easy_invoice_settings_nonce'] was read unguarded, so a
864 + // request without it raised "Undefined array key" before the check could run.
865 + $nonce = isset($_POST['easy_invoice_settings_nonce'])
866 + ? sanitize_text_field(wp_unslash($_POST['easy_invoice_settings_nonce']))
867 + : '';
868 + if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
869 + wp_die(esc_html__('Security check failed', 'easy-invoice'));
903 870 }
904 871
905 872
906 873 // Check permissions
907 874 if (!current_user_can('manage_options')) {
908 - wp_die(__('You do not have permission to perform this action', 'easy-invoice'));
875 + wp_die(esc_html__('You do not have permission to perform this action', 'easy-invoice'));
909 876 }
910 877
911 878 $posted_settings = $_POST['settings'] ?? [];
912 879
@@ -948,8 +915,30 @@
948 915 }
949 916 }
950 917 }
951 918
919 + // Fields other code adds to the Tax section for display only
920 + // (`easy_invoice_tax_section_additional_fields`) are saved with the
921 + // same rules — the reverse-charge switch lived only there and could
922 + // never be turned on from the screen.
923 + $extra_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], []);
924 + $known = [];
925 + foreach ($settings_config as $section_data) {
926 + foreach ((array) ($section_data['fields'] ?? []) as $k => $unused) { $known[$k] = true; }
927 + foreach ((array) ($section_data['subsections'] ?? []) as $sub) { foreach ((array) ($sub['fields'] ?? []) as $k => $unused) { $known[$k] = true; } }
928 + }
929 + foreach ((array) $extra_tax_fields as $option_key => $field_config) {
930 + if (!is_string($option_key) || isset($known[$option_key]) || !is_array($field_config)) {
931 + continue;
932 + }
933 + if (isset($posted_settings[$option_key])) {
934 + $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
935 + } elseif (($field_config['type'] ?? '') === 'checkbox') {
936 + update_option($option_key, 'no');
937 + $response['saved_options'][$option_key] = 'no';
938 + }
939 + }
940 +
952 941 // Handle special sections like payment methods
953 942 if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) {
954 943 $this->savePaymentSettings($posted_settings, $response);
955 944 }
@@ -958,8 +947,9 @@
958 947 wp_cache_flush();
959 948
960 949 // Clear settings cache
961 950 $this->settings_cache = null;
951 + self::$shared_settings_cache = null;
962 952
963 953 // Log the save action
964 954 $this->log('Settings saved successfully by user: ' . get_current_user_id());
965 955
@@ -1072,8 +1062,11 @@
1072 1062 // Return cached settings if available
1073 1063 if ($use_cache && is_array($this->settings_cache)) {
1074 1064 return $this->settings_cache;
1075 1065 }
1066 + if ($use_cache && is_array(self::$shared_settings_cache)) {
1067 + return $this->settings_cache = self::$shared_settings_cache;
1068 + }
1076 1069
1077 1070 $settings = [];
1078 1071 $config = $this->get_settings_fields_config();
1079 1072
@@ -1080,8 +1073,15 @@
1080 1073 foreach ($config as $section_id => $section_data) {
1081 1074 if ($section_id === 'payment' && isset($section_data['gateways'])) {
1082 1075 // Handle gateway fields specifically
1083 1076 foreach ($section_data['gateways'] as $gateway_id => $gateway_config) {
1077 + // The per-gateway display name is saved by savePaymentSettings()
1078 + // but was never loaded back, so the field always showed the default.
1079 + $display_key = 'easy_invoice_gateway_display_name_' . $gateway_id;
1080 + $display_val = get_option($display_key, '');
1081 + if ('' !== $display_val) {
1082 + $settings[$display_key] = $display_val;
1083 + }
1084 1084 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
1085 1085 foreach ($gateway_config['fields'] as $option_key => $field_data) {
1086 1086 $default_value = $field_data['default'] ?? '';
1087 1087 $settings[$option_key] = get_option($option_key, $default_value);
@@ -1143,8 +1143,9 @@
1143 1143 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1144 1144
1145 1145 // Cache settings
1146 1146 $this->settings_cache = $settings;
1147 + self::$shared_settings_cache = $settings;
1147 1148
1148 1149 // Allow third party to add/modify settings
1149 1150 return apply_filters('easy_invoice_settings', $settings);
1150 1151 }
@@ -1283,8 +1284,9 @@
1283 1284 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'),
1284 1285 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'),
1285 1286 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'),
1286 1287 'easy_invoice_text_total' => __('Total', 'easy-invoice'),
1288 + 'easy_invoice_text_line_total' => __('Total', 'easy-invoice'),
1287 1289 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'),
1288 1290 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'),
1289 1291 'easy_invoice_text_page' => __('Page', 'easy-invoice'),
1290 1292 'easy_invoice_text_print' => __('Print', 'easy-invoice'),
@@ -1324,46 +1326,9 @@
1324 1326 update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1325 1327 }
1326 1328
1327 1329 if (!get_option('easy_invoice_invoice_email_body')) {
1328 - update_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1329 -
1330 -<p>Dear {{client_name}},</p>
1331 -
1332 -<div class="highlight-box">
1333 - <p><strong>Invoice #{{invoice_number}}</strong><br>
1334 - <span class="amount-highlight">{{total_amount}}</span><br>
1335 - Due Date: <strong>{{due_date}}</strong></p>
1336 -</div>
1337 -
1338 -<p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment or visit the link below.</p>
1339 -
1340 -<div class="info-box">
1341 - <p><strong>📋 Payment Details:</strong><br>
1342 - • Invoice Number: {{invoice_number}}<br>
1343 - • Total Amount: {{total_amount}}<br>
1344 - • Due Date: {{due_date}}<br>
1345 - • Payment Terms: {{payment_terms}}</p>
1346 -</div>
1347 -
1348 -<div class="highlight-box">
1349 - <p><strong>🔗 View Invoice Online:</strong><br>
1350 - <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1351 -</div>
1352 -
1353 -<div class="warning-box">
1354 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1355 -</div>
1356 -
1357 -<p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
1358 -
1359 -<div class="divider"></div>
1360 -
1361 -<p>Thank you for your business!</p>
1362 -
1363 -<p>Best regards,<br>
1364 -<strong>{{company_name}}</strong><br>
1365 -{{company_email}}</p>', 'easy-invoice'));
1330 + update_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1366 1331 }
1367 1332
1368 1333 if (!get_option('easy_invoice_quote_email_enabled')) {
1369 1334 update_option('easy_invoice_quote_email_enabled', 'yes');
@@ -1373,40 +1338,9 @@
1373 1338 update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1374 1339 }
1375 1340
1376 1341 if (!get_option('easy_invoice_quote_email_body')) {
1377 - update_option('easy_invoice_quote_email_body', __('<h2>Your Quote is Ready</h2>
1378 -
1379 -<p>Dear {{client_name}},</p>
1380 -
1381 -<div class="highlight-box">
1382 - <p><strong>Quote #{{quote_number}}</strong><br>
1383 - Total Amount: <strong>{{total_amount}}</strong><br>
1384 - Valid Until: <strong>{{expiry_date}}</strong></p>
1385 -</div>
1386 -
1387 -<p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment or visit the link below.</p>
1388 -
1389 -<div class="info-box">
1390 - <p><strong>Quote Summary:</strong><br>
1391 - • Quote Number: {{quote_number}}<br>
1392 - • Total Amount: {{total_amount}}<br>
1393 - • Valid Until: {{expiry_date}}<br>
1394 - • Terms: {{payment_terms}}</p>
1395 -</div>
1396 -
1397 -<div class="highlight-box">
1398 - <p><strong>🔗 View Quote Online:</strong><br>
1399 - <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1400 -</div>
1401 -
1402 -<p>This quote is valid until {{expiry_date}}. If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1403 -
1404 -<p>We look forward to working with you!</p>
1405 -
1406 -<p>Best regards,<br>
1407 -<strong>{{company_name}}</strong><br>
1408 -{{company_email}}</p>', 'easy-invoice'));
1342 + update_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1409 1343 }
1410 1344
1411 1345 if (!get_option('easy_invoice_payment_email_enabled')) {
1412 1346 update_option('easy_invoice_payment_email_enabled', 'yes');
@@ -1416,45 +1350,9 @@
1416 1350 update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1417 1351 }
1418 1352
1419 1353 if (!get_option('easy_invoice_payment_email_body')) {
1420 - update_option('easy_invoice_payment_email_body', __('<h2>✅ Payment Received - Thank You!</h2>
1421 -
1422 -<p>Dear {{client_name}},</p>
1423 -
1424 -<div class="success-box">
1425 - <p><strong>Payment Confirmation</strong><br>
1426 - Invoice #{{invoice_number}}<br>
1427 - <span class="amount-highlight">{{payment_amount}}</span><br>
1428 - Payment Date: <strong>{{payment_date}}</strong><br>
1429 - Payment Method: <strong>{{payment_method}}</strong></p>
1430 -</div>
1431 -
1432 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
1433 -
1434 -<div class="info-box">
1435 - <p><strong>📊 Payment Details:</strong><br>
1436 - • Invoice Number: {{invoice_number}}<br>
1437 - • Amount Paid: {{payment_amount}}<br>
1438 - • Payment Date: {{payment_date}}<br>
1439 - • Payment Method: {{payment_method}}<br>
1440 - • Transaction ID: {{transaction_id}}</p>
1441 -</div>
1442 -
1443 -<div class="highlight-box">
1444 - <p><strong>🎉 Status: PAID</strong><br>
1445 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1446 -</div>
1447 -
1448 -<p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1449 -
1450 -<div class="divider"></div>
1451 -
1452 -<p>Thank you for choosing our services!</p>
1453 -
1454 -<p>Best regards,<br>
1455 -<strong>{{company_name}}</strong><br>
1456 -{{company_email}}</p>', 'easy-invoice'));
1354 + update_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1457 1355 }
1458 1356 }
1459 1357
1460 1358 /**
@@ -1508,9 +1406,9 @@
1508 1406 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1509 1407 }
1510 1408
1511 1409 // Get payment reminder settings
1512 - $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice-pro'));
1410 + $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice'));
1513 1411 $body = get_option('easy_invoice_pro_payment_reminder_body', '');
1514 1412
1515 1413 if (empty($body)) {
1516 1414 wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]);
@@ -1522,9 +1420,10 @@
1522 1420 'client_name' => 'Test Client',
1523 1421 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1524 1422 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1525 1423 'total_amount' => '$1,000.00',
1526 - 'due_date' => date('Y-m-d', strtotime('+7 days')),
1424 + 'amount_due' => '$1,000.00',
1425 + 'due_date' => wp_date('Y-m-d', strtotime('+7 days')),
1527 1426 ];
1528 1427
1529 1428 // Replace placeholders
1530 1429 foreach ($sample_data as $placeholder => $value) {
@@ -1604,16 +1503,36 @@
1604 1503 'quote_number' => 'TEST-Q-001',
1605 1504 'client_name' => 'Test Client',
1606 1505 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1607 1506 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1608 - 'total_amount' => '$1,000.00',
1609 - 'payment_amount' => '$1,000.00',
1610 - 'due_date' => date('Y-m-d', strtotime('+30 days')),
1611 - 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
1612 - 'payment_date' => date('Y-m-d'),
1507 + 'total_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1508 + 'payment_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1509 + 'due_date' => wp_date('Y-m-d', strtotime('+30 days')),
1510 + 'expiry_date' => wp_date('Y-m-d', strtotime('+30 days')),
1511 + 'payment_date' => current_time('Y-m-d'),
1613 1512 'payment_method' => 'Credit Card',
1614 1513 'transaction_id' => 'TXN-' . uniqid(),
1615 1514 'payment_terms' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
1515 + // Everything the stock templates can mention, so a test never shows a raw tag.
1516 + 'amount_due' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1517 + 'subtotal' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1518 + 'tax_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00',
1519 + 'discount_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00',
1520 + 'issue_date' => current_time('Y-m-d'),
1521 + 'invoice_title' => __('Sample invoice', 'easy-invoice'),
1522 + 'quote_title' => __('Sample quote', 'easy-invoice'),
1523 + 'invoice_url' => home_url('/'),
1524 + 'quote_url' => home_url('/'),
1525 + 'payment_url' => home_url('/'),
1526 + 'site_url' => home_url('/'),
1527 + 'admin_url' => admin_url(),
1528 + 'client_email' => '[email protected]',
1529 + 'client_first_name' => 'Test',
1530 + 'client_last_name' => 'Client',
1531 + 'client_address' => '1 Example Street',
1532 + 'company_phone' => get_option('easy_invoice_company_phone', ''),
1533 + 'company_address' => get_option('easy_invoice_company_address', ''),
1534 + 'company_website' => get_option('easy_invoice_company_website', ''),
1616 1535 ];
1617 1536
1618 1537 // Ensure all sample data values are strings
1619 1538 foreach ($sample_data as $key => $value) {
@@ -1723,9 +1642,9 @@
1723 1642 *
1724 1643 * @return string Quote footer text
1725 1644 */
1726 1645 public static function getQuoteFooterText(): string {
1727 - return get_option('easy_invoice_quote_footer_text', __('Thanks for choosing Easy Invoice', 'easy-invoice'));
1646 + return get_option('easy_invoice_quote_footer_text', __('Thank you for your business.', 'easy-invoice'));
1728 1647 }
1729 1648
1730 1649 /**
1731 1650 * Check if quote accept button should be shown
@@ -1806,41 +1725,9 @@
1806 1725 *
1807 1726 * @return string Invoice email body
1808 1727 */
1809 1728 public static function getInvoiceEmailBody(): string {
1810 - return get_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1811 -
1812 -<p>Dear {{client_name}},</p>
1813 -
1814 -<div class="highlight-box">
1815 - <p><strong>Invoice #{{invoice_number}}</strong><br>
1816 - <span class="amount-highlight">{{total_amount}}</span><br>
1817 - Due Date: <strong>{{due_date}}</strong></p>
1818 -</div>
1819 -
1820 -<p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
1821 -
1822 -<div class="info-box">
1823 - <p><strong>📋 Payment Details:</strong><br>
1824 - • Invoice Number: {{invoice_number}}<br>
1825 - • Total Amount: {{total_amount}}<br>
1826 - • Due Date: {{due_date}}<br>
1827 - • Payment Terms: {{payment_terms}}</p>
1828 -</div>
1829 -
1830 -<div class="warning-box">
1831 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1832 -</div>
1833 -
1834 -<p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1835 -
1836 -<div class="divider"></div>
1837 -
1838 -<p>Thank you for your business!</p>
1839 -
1840 -<p>Best regards,<br>
1841 -<strong>{{company_name}}</strong><br>
1842 -{{company_email}}</p>', 'easy-invoice'));
1729 + return get_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1843 1730 }
1844 1731
1845 1732 /**
1846 1733 * Check if quote available email is enabled
@@ -1865,41 +1752,9 @@
1865 1752 *
1866 1753 * @return string Quote email body
1867 1754 */
1868 1755 public static function getQuoteEmailBody(): string {
1869 - return get_option('easy_invoice_quote_email_body', __('<h2>📋 Your Quote is Ready</h2>
1870 -
1871 -<p>Dear {{client_name}},</p>
1872 -
1873 -<div class="highlight-box">
1874 - <p><strong>Quote #{{quote_number}}</strong><br>
1875 - <span class="amount-highlight">{{total_amount}}</span><br>
1876 - Valid Until: <strong>{{expiry_date}}</strong></p>
1877 -</div>
1878 -
1879 -<p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment.</p>
1880 -
1881 -<div class="info-box">
1882 - <p><strong>📋 Quote Summary:</strong><br>
1883 - • Quote Number: {{quote_number}}<br>
1884 - • Total Amount: {{total_amount}}<br>
1885 - • Valid Until: {{expiry_date}}<br>
1886 - • Terms: {{payment_terms}}</p>
1887 -</div>
1888 -
1889 -<div class="warning-box">
1890 - <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1891 -</div>
1892 -
1893 -<p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1894 -
1895 -<div class="divider"></div>
1896 -
1897 -<p>We look forward to working with you!</p>
1898 -
1899 -<p>Best regards,<br>
1900 -<strong>{{company_name}}</strong><br>
1901 -{{company_email}}</p>', 'easy-invoice'));
1756 + return get_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1902 1757 }
1903 1758
1904 1759 /**
1905 1760 * Check if payment received email is enabled
@@ -1924,45 +1779,9 @@
1924 1779 *
1925 1780 * @return string Payment email body
1926 1781 */
1927 1782 public static function getPaymentEmailBody(): string {
1928 - return get_option('easy_invoice_payment_email_body', __('<h2>✅ Payment Received - Thank You!</h2>
1929 -
1930 -<p>Dear {{client_name}},</p>
1931 -
1932 -<div class="success-box">
1933 - <p><strong>Payment Confirmation</strong><br>
1934 - Invoice #{{invoice_number}}<br>
1935 - <span class="amount-highlight">{{payment_amount}}</span><br>
1936 - Payment Date: <strong>{{payment_date}}</strong><br>
1937 - Payment Method: <strong>{{payment_method}}</strong></p>
1938 -</div>
1939 -
1940 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
1941 -
1942 -<div class="info-box">
1943 - <p><strong>📊 Payment Details:</strong><br>
1944 - • Invoice Number: {{invoice_number}}<br>
1945 - • Amount Paid: {{payment_amount}}<br>
1946 - • Payment Date: {{payment_date}}<br>
1947 - • Payment Method: {{payment_method}}<br>
1948 - • Transaction ID: {{transaction_id}}</p>
1949 -</div>
1950 -
1951 -<div class="highlight-box">
1952 - <p><strong>🎉 Status: PAID</strong><br>
1953 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1954 -</div>
1955 -
1956 -<p>If you have any questions about this payment or need a receipt, please do not hesitate to contact us.</p>
1957 -
1958 -<div class="divider"></div>
1959 -
1960 -<p>Thank you for choosing our services!</p>
1961 -
1962 -<p>Best regards,<br>
1963 -<strong>{{company_name}}</strong><br>
1964 -{{company_email}}</p>', 'easy-invoice'));
1783 + return get_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1965 1784 }
1966 1785
1967 1786 // ========================================
1968 1787 // TEXT SETTINGS HELPER FUNCTIONS
@@ -1984,11 +1803,74 @@
1984 1803 * @param string $key Text setting key
1985 1804 * @param string $default Default text
1986 1805 * @return string Custom text or default
1987 1806 */
1807 + /**
1808 + * Every document label the site can override, with its saved value.
1809 + *
1810 + * The getText*() methods below are what templates call; this list exists
1811 + * for anything that needs the whole set at once -- the Pro Translations
1812 + * page offers each saved label for translation.
1813 + *
1814 + * @return array<string,array{label:string,default:string,value:string}> keyed by setting key.
1815 + */
1816 + public static function getTextLabels(): array {
1817 + $labels = [
1818 + 'invoice' => __('Invoice', 'easy-invoice'),
1819 + 'invoices' => __('Invoices', 'easy-invoice'),
1820 + 'to' => __('To', 'easy-invoice'),
1821 + 'from' => __('From', 'easy-invoice'),
1822 + 'invoice_number' => __('Invoice Number', 'easy-invoice'),
1823 + 'invoice_date' => __('Invoice Date', 'easy-invoice'),
1824 + 'due_date' => __('Due Date', 'easy-invoice'),
1825 + 'total_due' => __('Total Due', 'easy-invoice'),
1826 + 'qty' => __('Qty', 'easy-invoice'),
1827 + 'service' => __('Service', 'easy-invoice'),
1828 + 'rate_price' => __('Rate', 'easy-invoice'),
1829 + 'adjust' => __('Adjust', 'easy-invoice'),
1830 + 'sub_total' => __('Sub Total', 'easy-invoice'),
1831 + 'total' => __('Total', 'easy-invoice'),
1832 + 'tax' => __('Tax', 'easy-invoice'),
1833 + 'discount' => __('Discount', 'easy-invoice'),
1834 + 'print' => __('Print', 'easy-invoice'),
1835 + 'download_pdf' => __('Download as PDF', 'easy-invoice'),
1836 + 'send_email' => __('Send Email', 'easy-invoice'),
1837 + 'pay_now' => __('Pay Now', 'easy-invoice'),
1838 + 'quote' => __('Quote', 'easy-invoice'),
1839 + 'quote_number' => __('Quote Number', 'easy-invoice'),
1840 + 'quote_date' => __('Quote Date', 'easy-invoice'),
1841 + 'valid_until' => __('Valid Until Date', 'easy-invoice'),
1842 + 'accept_quote' => __('Accept Quote', 'easy-invoice'),
1843 + 'decline_quote' => __('Decline Quote', 'easy-invoice'),
1844 + 'decline_reason' => __('Reason for declining', 'easy-invoice'),
1845 + ];
1846 + $out = [];
1847 + foreach ($labels as $key => $default) {
1848 + $out[$key] = [
1849 + 'label' => ucfirst(str_replace('_', ' ', $key)),
1850 + 'default' => $default,
1851 + 'value' => (string) get_option('easy_invoice_text_' . $key, $default),
1852 + ];
1853 + }
1854 + return $out;
1855 + }
1856 +
1988 1857 public static function getTextSetting(string $key, string $default): string {
1989 1858 $option_key = 'easy_invoice_text_' . $key;
1990 - return get_option($option_key, $default);
1859 + $value = (string) get_option($option_key, $default);
1860 +
1861 + /**
1862 + * Filter a document label after the saved override is applied.
1863 + *
1864 + * Saved labels are literal text and skip gettext, so anything that
1865 + * renders a document in another language (Pro's Client Language) uses
1866 + * this to run them through the active locale.
1867 + *
1868 + * @param string $value Label to show.
1869 + * @param string $key Setting key, e.g. 'total'.
1870 + * @param string $default Translated default.
1871 + */
1872 + return (string) apply_filters('easy_invoice_text_setting', $value, $key, $default);
1991 1873 }
1992 1874
1993 1875 // Invoice Text Settings
1994 1876 public static function getTextInvoice(): string {
@@ -2042,8 +1924,13 @@
2042 1924 public static function getTextTotal(): string {
2043 1925 return self::getTextSetting('total', __('Total', 'easy-invoice'));
2044 1926 }
2045 1927
1928 + /** Heading of the line-item amount column, separate from the grand total. */
1929 + public static function getTextLineTotal(): string {
1930 + return self::getTextSetting('line_total', __('Total', 'easy-invoice'));
1931 + }
1932 +
2046 1933 public static function getTextTax(): string {
2047 1934 return self::getTextSetting('tax', __('Tax', 'easy-invoice'));
2048 1935 }
2049 1936
@@ -2200,8 +2087,9 @@
2200 2087 // Update the next invoice number counter to continue from the last regenerated number + 1
2201 2088 update_option('easy_invoice_next_invoice_number', $current_number);
2202 2089
2203 2090 wp_send_json_success([
2091 + /* translators: %d: number regenerated. */
2204 2092 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count),
2205 2093 'regenerated_count' => $regenerated_count,
2206 2094 'next_number' => $current_number
2207 2095 ]);
@@ -2259,8 +2147,9 @@
2259 2147
2260 2148 update_option('easy_invoice_next_quote_number', $current_number);
2261 2149
2262 2150 wp_send_json_success([
2151 + /* translators: %d: number regenerated. */
2263 2152 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count),
2264 2153 'regenerated_count' => $regenerated_count,
2265 2154 'next_number' => $current_number
2266 2155 ]);