PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.2
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 +248 -336 2.1.18 → 2.4.2 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,47 +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 - <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
517 -</div>
518 -
519 -<div class="warning-box">
520 - <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
521 -</div>
522 -
523 -<p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
524 -
525 -<div class="divider"></div>
526 -
527 -<p>We look forward to working with you!</p>
528 -
529 -<p>Best regards,<br>
530 -<strong>{{company_name}}</strong><br>
531 -{{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'],
532 492 'easy_invoice_quote_test_template' => [
533 493 'label' => __('Test Quote Email', 'easy-invoice'),
534 494 'type' => 'test_template_email',
535 495 'default' => '',
@@ -544,45 +504,9 @@
544 504 'description' => __('Configure email sent when a payment is received', 'easy-invoice'),
545 505 'fields' => [
546 506 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
547 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'],
548 - 'easy_invoice_payment_email_body' => ['label' => __('Email Body', 'easy-invoice'), 'type' => 'wp_editor', 'default' => __('<h2>✅ Payment Received - Thank You!</h2>
549 -
550 -<p>Dear {{client_name}},</p>
551 -
552 -<div class="success-box">
553 - <p><strong>Payment Confirmation</strong><br>
554 - Invoice #{{invoice_number}}<br>
555 - <span class="amount-highlight">{{payment_amount}}</span><br>
556 - Payment Date: <strong>{{payment_date}}</strong><br>
557 - Payment Method: <strong>{{payment_method}}</strong></p>
558 -</div>
559 -
560 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
561 -
562 -<div class="info-box">
563 - <p><strong>📊 Payment Details:</strong><br>
564 - • Invoice Number: {{invoice_number}}<br>
565 - • Amount Paid: {{payment_amount}}<br>
566 - • Payment Date: {{payment_date}}<br>
567 - • Payment Method: {{payment_method}}<br>
568 - • Transaction ID: {{transaction_id}}</p>
569 -</div>
570 -
571 -<div class="highlight-box">
572 - <p><strong>🎉 Status: PAID</strong><br>
573 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
574 -</div>
575 -
576 -<p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
577 -
578 -<div class="divider"></div>
579 -
580 -<p>Thank you for choosing our services!</p>
581 -
582 -<p>Best regards,<br>
583 -<strong>{{company_name}}</strong><br>
584 -{{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'],
585 509 'easy_invoice_payment_test_template' => [
586 510 'label' => __('Test Payment Email', 'easy-invoice'),
587 511 'type' => 'test_template_email',
588 512 'default' => '',
@@ -618,8 +542,9 @@
618 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')],
619 543 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')],
620 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')],
621 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')],
622 547 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')],
623 548 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')],
624 549 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
625 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')],
@@ -653,9 +578,10 @@
653 578 'col_span' => 'sm:col-span-3'
654 579 ],
655 580
656 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' ],
657 - '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')],
658 584 ]
659 585 ],
660 586 ];
661 587
@@ -892,22 +818,62 @@
892 818 }
893 819 }
894 820
895 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 + /**
896 858 * Save settings
897 859 *
898 860 * @return void
899 861 */
900 862 public function saveSettings() {
901 - // Verify nonce
902 - if (!wp_verify_nonce($_POST['easy_invoice_settings_nonce'], 'easy_invoice_settings')) {
903 - 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'));
904 870 }
905 871
906 872
907 873 // Check permissions
908 874 if (!current_user_can('manage_options')) {
909 - 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'));
910 876 }
911 877
912 878 $posted_settings = $_POST['settings'] ?? [];
913 879
@@ -949,8 +915,30 @@
949 915 }
950 916 }
951 917 }
952 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 +
953 941 // Handle special sections like payment methods
954 942 if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) {
955 943 $this->savePaymentSettings($posted_settings, $response);
956 944 }
@@ -959,8 +947,9 @@
959 947 wp_cache_flush();
960 948
961 949 // Clear settings cache
962 950 $this->settings_cache = null;
951 + self::$shared_settings_cache = null;
963 952
964 953 // Log the save action
965 954 $this->log('Settings saved successfully by user: ' . get_current_user_id());
966 955
@@ -1073,8 +1062,11 @@
1073 1062 // Return cached settings if available
1074 1063 if ($use_cache && is_array($this->settings_cache)) {
1075 1064 return $this->settings_cache;
1076 1065 }
1066 + if ($use_cache && is_array(self::$shared_settings_cache)) {
1067 + return $this->settings_cache = self::$shared_settings_cache;
1068 + }
1077 1069
1078 1070 $settings = [];
1079 1071 $config = $this->get_settings_fields_config();
1080 1072
@@ -1081,8 +1073,15 @@
1081 1073 foreach ($config as $section_id => $section_data) {
1082 1074 if ($section_id === 'payment' && isset($section_data['gateways'])) {
1083 1075 // Handle gateway fields specifically
1084 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 + }
1085 1084 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
1086 1085 foreach ($gateway_config['fields'] as $option_key => $field_data) {
1087 1086 $default_value = $field_data['default'] ?? '';
1088 1087 $settings[$option_key] = get_option($option_key, $default_value);
@@ -1144,8 +1143,9 @@
1144 1143 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1145 1144
1146 1145 // Cache settings
1147 1146 $this->settings_cache = $settings;
1147 + self::$shared_settings_cache = $settings;
1148 1148
1149 1149 // Allow third party to add/modify settings
1150 1150 return apply_filters('easy_invoice_settings', $settings);
1151 1151 }
@@ -1163,8 +1163,34 @@
1163 1163 /**
1164 1164 * Initialize default settings
1165 1165 */
1166 1166 public function initializeSettings() {
1167 + // One-time migration (2.1.22): strip the leftover
1168 + // "<strong>Shortcode:</strong> <code>[easy_(invoice|quote)_url …]</code>"
1169 + // reference line from saved email bodies. Originally intended as admin-side
1170 + // documentation, but it lived inside the email template and got emailed to
1171 + // clients verbatim. Guarded by a flag option so each install runs at most once.
1172 + //
1173 + // The regex tolerates the optional surrounding <p>...</p> because WP's
1174 + // wp_kses_post may normalise the wrapping when the option is saved.
1175 + if (!get_option('easy_invoice_email_shortcode_hint_removed_v2')) {
1176 + foreach (['easy_invoice_invoice_email_body', 'easy_invoice_quote_email_body'] as $opt_key) {
1177 + $body = get_option($opt_key, '');
1178 + if (!is_string($body) || $body === '') {
1179 + continue;
1180 + }
1181 + $cleaned = preg_replace(
1182 + '#\s*(?:<p>\s*)?<strong>\s*Shortcode\s*:\s*</strong>\s*<code>\s*\[easy_(?:invoice|quote)_url[^\]]*\]\s*</code>(?:\s*</p>)?\s*#i',
1183 + '',
1184 + $body
1185 + );
1186 + if ($cleaned !== null && $cleaned !== $body) {
1187 + update_option($opt_key, $cleaned);
1188 + }
1189 + }
1190 + update_option('easy_invoice_email_shortcode_hint_removed_v2', 1);
1191 + }
1192 +
1167 1193 // Initialize invoice number settings if not already set
1168 1194 if (!get_option('easy_invoice_invoice_prefix')) {
1169 1195 update_option('easy_invoice_invoice_prefix', 'INV-');
1170 1196 }
@@ -1258,8 +1284,9 @@
1258 1284 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'),
1259 1285 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'),
1260 1286 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'),
1261 1287 'easy_invoice_text_total' => __('Total', 'easy-invoice'),
1288 + 'easy_invoice_text_line_total' => __('Total', 'easy-invoice'),
1262 1289 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'),
1263 1290 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'),
1264 1291 'easy_invoice_text_page' => __('Page', 'easy-invoice'),
1265 1292 'easy_invoice_text_print' => __('Print', 'easy-invoice'),
@@ -1299,47 +1326,9 @@
1299 1326 update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1300 1327 }
1301 1328
1302 1329 if (!get_option('easy_invoice_invoice_email_body')) {
1303 - update_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1304 -
1305 -<p>Dear {{client_name}},</p>
1306 -
1307 -<div class="highlight-box">
1308 - <p><strong>Invoice #{{invoice_number}}</strong><br>
1309 - <span class="amount-highlight">{{total_amount}}</span><br>
1310 - Due Date: <strong>{{due_date}}</strong></p>
1311 -</div>
1312 -
1313 -<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>
1314 -
1315 -<div class="info-box">
1316 - <p><strong>📋 Payment Details:</strong><br>
1317 - • Invoice Number: {{invoice_number}}<br>
1318 - • Total Amount: {{total_amount}}<br>
1319 - • Due Date: {{due_date}}<br>
1320 - • Payment Terms: {{payment_terms}}</p>
1321 -</div>
1322 -
1323 -<div class="highlight-box">
1324 - <p><strong>🔗 View Invoice Online:</strong><br>
1325 - <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1326 - <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1327 -</div>
1328 -
1329 -<div class="warning-box">
1330 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1331 -</div>
1332 -
1333 -<p>If you have any questions about this invoice, please don\'t hesitate to contact us.</p>
1334 -
1335 -<div class="divider"></div>
1336 -
1337 -<p>Thank you for your business!</p>
1338 -
1339 -<p>Best regards,<br>
1340 -<strong>{{company_name}}</strong><br>
1341 -{{company_email}}</p>', 'easy-invoice'));
1330 + update_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1342 1331 }
1343 1332
1344 1333 if (!get_option('easy_invoice_quote_email_enabled')) {
1345 1334 update_option('easy_invoice_quote_email_enabled', 'yes');
@@ -1349,41 +1338,9 @@
1349 1338 update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1350 1339 }
1351 1340
1352 1341 if (!get_option('easy_invoice_quote_email_body')) {
1353 - update_option('easy_invoice_quote_email_body', __('<h2>Your Quote is Ready</h2>
1354 -
1355 -<p>Dear {{client_name}},</p>
1356 -
1357 -<div class="highlight-box">
1358 - <p><strong>Quote #{{quote_number}}</strong><br>
1359 - Total Amount: <strong>{{total_amount}}</strong><br>
1360 - Valid Until: <strong>{{expiry_date}}</strong></p>
1361 -</div>
1362 -
1363 -<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>
1364 -
1365 -<div class="info-box">
1366 - <p><strong>Quote Summary:</strong><br>
1367 - • Quote Number: {{quote_number}}<br>
1368 - • Total Amount: {{total_amount}}<br>
1369 - • Valid Until: {{expiry_date}}<br>
1370 - • Terms: {{payment_terms}}</p>
1371 -</div>
1372 -
1373 -<div class="highlight-box">
1374 - <p><strong>🔗 View Quote Online:</strong><br>
1375 - <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1376 - <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
1377 -</div>
1378 -
1379 -<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>
1380 -
1381 -<p>We look forward to working with you!</p>
1382 -
1383 -<p>Best regards,<br>
1384 -<strong>{{company_name}}</strong><br>
1385 -{{company_email}}</p>', 'easy-invoice'));
1342 + update_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1386 1343 }
1387 1344
1388 1345 if (!get_option('easy_invoice_payment_email_enabled')) {
1389 1346 update_option('easy_invoice_payment_email_enabled', 'yes');
@@ -1393,45 +1350,9 @@
1393 1350 update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1394 1351 }
1395 1352
1396 1353 if (!get_option('easy_invoice_payment_email_body')) {
1397 - update_option('easy_invoice_payment_email_body', __('<h2>✅ Payment Received - Thank You!</h2>
1398 -
1399 -<p>Dear {{client_name}},</p>
1400 -
1401 -<div class="success-box">
1402 - <p><strong>Payment Confirmation</strong><br>
1403 - Invoice #{{invoice_number}}<br>
1404 - <span class="amount-highlight">{{payment_amount}}</span><br>
1405 - Payment Date: <strong>{{payment_date}}</strong><br>
1406 - Payment Method: <strong>{{payment_method}}</strong></p>
1407 -</div>
1408 -
1409 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
1410 -
1411 -<div class="info-box">
1412 - <p><strong>📊 Payment Details:</strong><br>
1413 - • Invoice Number: {{invoice_number}}<br>
1414 - • Amount Paid: {{payment_amount}}<br>
1415 - • Payment Date: {{payment_date}}<br>
1416 - • Payment Method: {{payment_method}}<br>
1417 - • Transaction ID: {{transaction_id}}</p>
1418 -</div>
1419 -
1420 -<div class="highlight-box">
1421 - <p><strong>🎉 Status: PAID</strong><br>
1422 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1423 -</div>
1424 -
1425 -<p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1426 -
1427 -<div class="divider"></div>
1428 -
1429 -<p>Thank you for choosing our services!</p>
1430 -
1431 -<p>Best regards,<br>
1432 -<strong>{{company_name}}</strong><br>
1433 -{{company_email}}</p>', 'easy-invoice'));
1354 + update_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1434 1355 }
1435 1356 }
1436 1357
1437 1358 /**
@@ -1485,9 +1406,9 @@
1485 1406 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1486 1407 }
1487 1408
1488 1409 // Get payment reminder settings
1489 - $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'));
1490 1411 $body = get_option('easy_invoice_pro_payment_reminder_body', '');
1491 1412
1492 1413 if (empty($body)) {
1493 1414 wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]);
@@ -1499,9 +1420,10 @@
1499 1420 'client_name' => 'Test Client',
1500 1421 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1501 1422 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1502 1423 'total_amount' => '$1,000.00',
1503 - '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')),
1504 1426 ];
1505 1427
1506 1428 // Replace placeholders
1507 1429 foreach ($sample_data as $placeholder => $value) {
@@ -1581,16 +1503,36 @@
1581 1503 'quote_number' => 'TEST-Q-001',
1582 1504 'client_name' => 'Test Client',
1583 1505 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1584 1506 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1585 - 'total_amount' => '$1,000.00',
1586 - 'payment_amount' => '$1,000.00',
1587 - 'due_date' => date('Y-m-d', strtotime('+30 days')),
1588 - 'expiry_date' => date('Y-m-d', strtotime('+30 days')),
1589 - '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'),
1590 1512 'payment_method' => 'Credit Card',
1591 1513 'transaction_id' => 'TXN-' . uniqid(),
1592 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', ''),
1593 1535 ];
1594 1536
1595 1537 // Ensure all sample data values are strings
1596 1538 foreach ($sample_data as $key => $value) {
@@ -1700,9 +1642,9 @@
1700 1642 *
1701 1643 * @return string Quote footer text
1702 1644 */
1703 1645 public static function getQuoteFooterText(): string {
1704 - 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'));
1705 1647 }
1706 1648
1707 1649 /**
1708 1650 * Check if quote accept button should be shown
@@ -1783,41 +1725,9 @@
1783 1725 *
1784 1726 * @return string Invoice email body
1785 1727 */
1786 1728 public static function getInvoiceEmailBody(): string {
1787 - return get_option('easy_invoice_invoice_email_body', __('<h2>📄 Your Invoice is Ready</h2>
1788 -
1789 -<p>Dear {{client_name}},</p>
1790 -
1791 -<div class="highlight-box">
1792 - <p><strong>Invoice #{{invoice_number}}</strong><br>
1793 - <span class="amount-highlight">{{total_amount}}</span><br>
1794 - Due Date: <strong>{{due_date}}</strong></p>
1795 -</div>
1796 -
1797 -<p>Your invoice has been prepared and is ready for payment. You can view and download the complete invoice from the attachment.</p>
1798 -
1799 -<div class="info-box">
1800 - <p><strong>📋 Payment Details:</strong><br>
1801 - • Invoice Number: {{invoice_number}}<br>
1802 - • Total Amount: {{total_amount}}<br>
1803 - • Due Date: {{due_date}}<br>
1804 - • Payment Terms: {{payment_terms}}</p>
1805 -</div>
1806 -
1807 -<div class="warning-box">
1808 - <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1809 -</div>
1810 -
1811 -<p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1812 -
1813 -<div class="divider"></div>
1814 -
1815 -<p>Thank you for your business!</p>
1816 -
1817 -<p>Best regards,<br>
1818 -<strong>{{company_name}}</strong><br>
1819 -{{company_email}}</p>', 'easy-invoice'));
1729 + return get_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1820 1730 }
1821 1731
1822 1732 /**
1823 1733 * Check if quote available email is enabled
@@ -1842,41 +1752,9 @@
1842 1752 *
1843 1753 * @return string Quote email body
1844 1754 */
1845 1755 public static function getQuoteEmailBody(): string {
1846 - return get_option('easy_invoice_quote_email_body', __('<h2>📋 Your Quote is Ready</h2>
1847 -
1848 -<p>Dear {{client_name}},</p>
1849 -
1850 -<div class="highlight-box">
1851 - <p><strong>Quote #{{quote_number}}</strong><br>
1852 - <span class="amount-highlight">{{total_amount}}</span><br>
1853 - Valid Until: <strong>{{expiry_date}}</strong></p>
1854 -</div>
1855 -
1856 -<p>We have prepared a detailed quote for your project. You can view and download the complete quote from the attachment.</p>
1857 -
1858 -<div class="info-box">
1859 - <p><strong>📋 Quote Summary:</strong><br>
1860 - • Quote Number: {{quote_number}}<br>
1861 - • Total Amount: {{total_amount}}<br>
1862 - • Valid Until: {{expiry_date}}<br>
1863 - • Terms: {{payment_terms}}</p>
1864 -</div>
1865 -
1866 -<div class="warning-box">
1867 - <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1868 -</div>
1869 -
1870 -<p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1871 -
1872 -<div class="divider"></div>
1873 -
1874 -<p>We look forward to working with you!</p>
1875 -
1876 -<p>Best regards,<br>
1877 -<strong>{{company_name}}</strong><br>
1878 -{{company_email}}</p>', 'easy-invoice'));
1756 + return get_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1879 1757 }
1880 1758
1881 1759 /**
1882 1760 * Check if payment received email is enabled
@@ -1901,45 +1779,9 @@
1901 1779 *
1902 1780 * @return string Payment email body
1903 1781 */
1904 1782 public static function getPaymentEmailBody(): string {
1905 - return get_option('easy_invoice_payment_email_body', __('<h2>✅ Payment Received - Thank You!</h2>
1906 -
1907 -<p>Dear {{client_name}},</p>
1908 -
1909 -<div class="success-box">
1910 - <p><strong>Payment Confirmation</strong><br>
1911 - Invoice #{{invoice_number}}<br>
1912 - <span class="amount-highlight">{{payment_amount}}</span><br>
1913 - Payment Date: <strong>{{payment_date}}</strong><br>
1914 - Payment Method: <strong>{{payment_method}}</strong></p>
1915 -</div>
1916 -
1917 -<p>We have successfully received your payment. Thank you for your prompt payment!</p>
1918 -
1919 -<div class="info-box">
1920 - <p><strong>📊 Payment Details:</strong><br>
1921 - • Invoice Number: {{invoice_number}}<br>
1922 - • Amount Paid: {{payment_amount}}<br>
1923 - • Payment Date: {{payment_date}}<br>
1924 - • Payment Method: {{payment_method}}<br>
1925 - • Transaction ID: {{transaction_id}}</p>
1926 -</div>
1927 -
1928 -<div class="highlight-box">
1929 - <p><strong>🎉 Status: PAID</strong><br>
1930 - Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1931 -</div>
1932 -
1933 -<p>If you have any questions about this payment or need a receipt, please do not hesitate to contact us.</p>
1934 -
1935 -<div class="divider"></div>
1936 -
1937 -<p>Thank you for choosing our services!</p>
1938 -
1939 -<p>Best regards,<br>
1940 -<strong>{{company_name}}</strong><br>
1941 -{{company_email}}</p>', 'easy-invoice'));
1783 + return get_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1942 1784 }
1943 1785
1944 1786 // ========================================
1945 1787 // TEXT SETTINGS HELPER FUNCTIONS
@@ -1961,11 +1803,74 @@
1961 1803 * @param string $key Text setting key
1962 1804 * @param string $default Default text
1963 1805 * @return string Custom text or default
1964 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 +
1965 1857 public static function getTextSetting(string $key, string $default): string {
1966 1858 $option_key = 'easy_invoice_text_' . $key;
1967 - 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);
1968 1873 }
1969 1874
1970 1875 // Invoice Text Settings
1971 1876 public static function getTextInvoice(): string {
@@ -2019,8 +1924,13 @@
2019 1924 public static function getTextTotal(): string {
2020 1925 return self::getTextSetting('total', __('Total', 'easy-invoice'));
2021 1926 }
2022 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 +
2023 1933 public static function getTextTax(): string {
2024 1934 return self::getTextSetting('tax', __('Tax', 'easy-invoice'));
2025 1935 }
2026 1936
@@ -2177,8 +2087,9 @@
2177 2087 // Update the next invoice number counter to continue from the last regenerated number + 1
2178 2088 update_option('easy_invoice_next_invoice_number', $current_number);
2179 2089
2180 2090 wp_send_json_success([
2091 + /* translators: %d: number regenerated. */
2181 2092 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count),
2182 2093 'regenerated_count' => $regenerated_count,
2183 2094 'next_number' => $current_number
2184 2095 ]);
@@ -2236,8 +2147,9 @@
2236 2147
2237 2148 update_option('easy_invoice_next_quote_number', $current_number);
2238 2149
2239 2150 wp_send_json_success([
2151 + /* translators: %d: number regenerated. */
2240 2152 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count),
2241 2153 'regenerated_count' => $regenerated_count,
2242 2154 'next_number' => $current_number
2243 2155 ]);