PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
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 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | includes/Controllers/SettingsController.php +183 -332 2.3.42.4.0 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 */
@@ -181,8 +198,12 @@
181 198 'easy_invoice_company_name' => ['label' => __('Company Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
182 199 'easy_invoice_company_email' => ['label' => __('Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-6' ],
183 200 'easy_invoice_company_phone' => ['label' => __('Phone Number', 'easy-invoice'), 'type' => 'tel', 'default' => '', 'col_span' => 'sm:col-span-6' ],
184 201 'easy_invoice_company_website' => ['label' => __('Website', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6' ],
202 + // Both are required by EN 16931 and are what makes an intra-EU
203 + // reverse charge determinable. See Services\TaxTreatment.
204 + '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') ],
205 + '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 206 'easy_invoice_company_address' => ['label' => __('Address', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ],
186 207 'easy_invoice_tax_number' => ['label' => __('Tax ID / VAT Number', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6' ],
187 208 'easy_invoice_company_logo' => ['label' => __('Company Logo', 'easy-invoice'), 'type' => 'image', 'default' => '', 'col_span' => 'sm:col-span-6' ],
188 209 ]
@@ -282,9 +303,9 @@
282 303 ],
283 304 'easy_invoice_quote_footer_text' => [
284 305 'label' => __('Footer Text', 'easy-invoice'),
285 306 'type' => 'wp_editor',
286 - 'default' => __('Thanks for choosing Easy Invoice', 'easy-invoice'),
307 + 'default' => __('Thank you for your business.', 'easy-invoice'),
287 308 '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 309 'col_span' => 'sm:col-span-6'
289 310 ],
290 311 'easy_invoice_quote_accept_button' => [
@@ -422,8 +443,15 @@
422 443 'easy_invoice_email_reply_to' => ['label' => __('Reply-To Email', 'easy-invoice'), 'type' => 'email', 'default' => '', 'col_span' => 'sm:col-span-3'],
423 444 'easy_invoice_email_reply_to_name' => ['label' => __('Reply-To Name', 'easy-invoice'), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-3'],
424 445 'easy_invoice_enable_email_styling' => ['label' => __('Enable HTML Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
425 446 'easy_invoice_bcc_admin' => ['label' => __('BCC Admin on All Emails', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'no', 'col_span' => 'sm:col-span-6'],
447 + // Off by default: attaching a document changes what every
448 + // customer receives and makes messages much larger, which
449 + // some SMTP relays reject. See EmailManager::shouldAttachInvoicePdf().
450 + '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')],
451 + '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'],
452 + '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')],
453 + '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 454 '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 455 'easy_invoice_email_logo' => ['label' => __('Email Logo URL', 'easy-invoice'), 'type' => 'url', 'default' => '', 'col_span' => 'sm:col-span-6'],
428 456 'easy_invoice_email_footer_text' => ['label' => __('Email Footer Text', 'easy-invoice'), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6'],
429 457 'easy_invoice_test_email' => [
@@ -440,41 +468,9 @@
440 468 'description' => __('Configure email sent when an invoice is available for the client', 'easy-invoice'),
441 469 'fields' => [
442 470 'easy_invoice_invoice_email_enabled' => ['label' => __('Enable Invoice Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
443 471 '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'],
472 + '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 473 'easy_invoice_invoice_test_template' => [
478 474 'label' => __('Test Invoice Email', 'easy-invoice'),
479 475 'type' => 'test_template_email',
480 476 'default' => '',
@@ -489,46 +485,9 @@
489 485 'description' => __('Configure email sent when a quote is available for the client', 'easy-invoice'),
490 486 'fields' => [
491 487 'easy_invoice_quote_email_enabled' => ['label' => __('Enable Quote Available Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
492 488 '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'],
489 + '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 490 'easy_invoice_quote_test_template' => [
532 491 'label' => __('Test Quote Email', 'easy-invoice'),
533 492 'type' => 'test_template_email',
534 493 'default' => '',
@@ -543,45 +502,9 @@
543 502 'description' => __('Configure email sent when a payment is received', 'easy-invoice'),
544 503 'fields' => [
545 504 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'],
546 505 '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'],
506 + '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 507 'easy_invoice_payment_test_template' => [
585 508 'label' => __('Test Payment Email', 'easy-invoice'),
586 509 'type' => 'test_template_email',
587 510 'default' => '',
@@ -617,8 +540,9 @@
617 540 '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 541 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')],
619 542 '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 543 'easy_invoice_text_total' => ['label' => __('Total', 'easy-invoice'), 'type' => 'text', 'default' => __('Total', 'easy-invoice'), 'col_span' => '', 'description' => __('Total label', 'easy-invoice')],
544 + '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 545 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')],
622 546 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')],
623 547 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')],
624 548 '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,8 +577,9 @@
653 577 ],
654 578
655 579 '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 580 'easy_invoice_payment_reminder_days' => ['label' => __('Payment Reminder Days', 'easy-invoice'), 'type' => 'number', 'default' => 3, 'col_span' => 'sm:col-span-3'],
581 + '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 582 ]
658 583 ],
659 584 ];
660 585
@@ -896,17 +821,21 @@
896 821 *
897 822 * @return void
898 823 */
899 824 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'));
825 + // Verify nonce. $_POST['easy_invoice_settings_nonce'] was read unguarded, so a
826 + // request without it raised "Undefined array key" before the check could run.
827 + $nonce = isset($_POST['easy_invoice_settings_nonce'])
828 + ? sanitize_text_field(wp_unslash($_POST['easy_invoice_settings_nonce']))
829 + : '';
830 + if (!wp_verify_nonce($nonce, 'easy_invoice_settings')) {
831 + wp_die(esc_html__('Security check failed', 'easy-invoice'));
903 832 }
904 833
905 834
906 835 // Check permissions
907 836 if (!current_user_can('manage_options')) {
908 - wp_die(__('You do not have permission to perform this action', 'easy-invoice'));
837 + wp_die(esc_html__('You do not have permission to perform this action', 'easy-invoice'));
909 838 }
910 839
911 840 $posted_settings = $_POST['settings'] ?? [];
912 841
@@ -948,8 +877,30 @@
948 877 }
949 878 }
950 879 }
951 880
881 + // Fields other code adds to the Tax section for display only
882 + // (`easy_invoice_tax_section_additional_fields`) are saved with the
883 + // same rules — the reverse-charge switch lived only there and could
884 + // never be turned on from the screen.
885 + $extra_tax_fields = apply_filters('easy_invoice_tax_section_additional_fields', [], []);
886 + $known = [];
887 + foreach ($settings_config as $section_data) {
888 + foreach ((array) ($section_data['fields'] ?? []) as $k => $unused) { $known[$k] = true; }
889 + foreach ((array) ($section_data['subsections'] ?? []) as $sub) { foreach ((array) ($sub['fields'] ?? []) as $k => $unused) { $known[$k] = true; } }
890 + }
891 + foreach ((array) $extra_tax_fields as $option_key => $field_config) {
892 + if (!is_string($option_key) || isset($known[$option_key]) || !is_array($field_config)) {
893 + continue;
894 + }
895 + if (isset($posted_settings[$option_key])) {
896 + $this->sanitizeAndSaveSetting($option_key, $field_config, $posted_settings[$option_key], $response);
897 + } elseif (($field_config['type'] ?? '') === 'checkbox') {
898 + update_option($option_key, 'no');
899 + $response['saved_options'][$option_key] = 'no';
900 + }
901 + }
902 +
952 903 // Handle special sections like payment methods
953 904 if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) {
954 905 $this->savePaymentSettings($posted_settings, $response);
955 906 }
@@ -958,8 +909,9 @@
958 909 wp_cache_flush();
959 910
960 911 // Clear settings cache
961 912 $this->settings_cache = null;
913 + self::$shared_settings_cache = null;
962 914
963 915 // Log the save action
964 916 $this->log('Settings saved successfully by user: ' . get_current_user_id());
965 917
@@ -1072,8 +1024,11 @@
1072 1024 // Return cached settings if available
1073 1025 if ($use_cache && is_array($this->settings_cache)) {
1074 1026 return $this->settings_cache;
1075 1027 }
1028 + if ($use_cache && is_array(self::$shared_settings_cache)) {
1029 + return $this->settings_cache = self::$shared_settings_cache;
1030 + }
1076 1031
1077 1032 $settings = [];
1078 1033 $config = $this->get_settings_fields_config();
1079 1034
@@ -1080,8 +1035,15 @@
1080 1035 foreach ($config as $section_id => $section_data) {
1081 1036 if ($section_id === 'payment' && isset($section_data['gateways'])) {
1082 1037 // Handle gateway fields specifically
1083 1038 foreach ($section_data['gateways'] as $gateway_id => $gateway_config) {
1039 + // The per-gateway display name is saved by savePaymentSettings()
1040 + // but was never loaded back, so the field always showed the default.
1041 + $display_key = 'easy_invoice_gateway_display_name_' . $gateway_id;
1042 + $display_val = get_option($display_key, '');
1043 + if ('' !== $display_val) {
1044 + $settings[$display_key] = $display_val;
1045 + }
1084 1046 if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) {
1085 1047 foreach ($gateway_config['fields'] as $option_key => $field_data) {
1086 1048 $default_value = $field_data['default'] ?? '';
1087 1049 $settings[$option_key] = get_option($option_key, $default_value);
@@ -1143,8 +1105,9 @@
1143 1105 $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []);
1144 1106
1145 1107 // Cache settings
1146 1108 $this->settings_cache = $settings;
1109 + self::$shared_settings_cache = $settings;
1147 1110
1148 1111 // Allow third party to add/modify settings
1149 1112 return apply_filters('easy_invoice_settings', $settings);
1150 1113 }
@@ -1283,8 +1246,9 @@
1283 1246 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'),
1284 1247 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'),
1285 1248 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'),
1286 1249 'easy_invoice_text_total' => __('Total', 'easy-invoice'),
1250 + 'easy_invoice_text_line_total' => __('Total', 'easy-invoice'),
1287 1251 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'),
1288 1252 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'),
1289 1253 'easy_invoice_text_page' => __('Page', 'easy-invoice'),
1290 1254 'easy_invoice_text_print' => __('Print', 'easy-invoice'),
@@ -1324,46 +1288,9 @@
1324 1288 update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice'));
1325 1289 }
1326 1290
1327 1291 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'));
1292 + update_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1366 1293 }
1367 1294
1368 1295 if (!get_option('easy_invoice_quote_email_enabled')) {
1369 1296 update_option('easy_invoice_quote_email_enabled', 'yes');
@@ -1373,40 +1300,9 @@
1373 1300 update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice'));
1374 1301 }
1375 1302
1376 1303 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'));
1304 + update_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1409 1305 }
1410 1306
1411 1307 if (!get_option('easy_invoice_payment_email_enabled')) {
1412 1308 update_option('easy_invoice_payment_email_enabled', 'yes');
@@ -1416,45 +1312,9 @@
1416 1312 update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice'));
1417 1313 }
1418 1314
1419 1315 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'));
1316 + update_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1457 1317 }
1458 1318 }
1459 1319
1460 1320 /**
@@ -1508,9 +1368,9 @@
1508 1368 wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]);
1509 1369 }
1510 1370
1511 1371 // Get payment reminder settings
1512 - $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice-pro'));
1372 + $subject = get_option('easy_invoice_pro_payment_reminder_subject', __('A friendly reminder - Invoice #{{invoice_number}}', 'easy-invoice'));
1513 1373 $body = get_option('easy_invoice_pro_payment_reminder_body', '');
1514 1374
1515 1375 if (empty($body)) {
1516 1376 wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]);
@@ -1522,9 +1382,10 @@
1522 1382 'client_name' => 'Test Client',
1523 1383 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1524 1384 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')),
1525 1385 'total_amount' => '$1,000.00',
1526 - 'due_date' => date('Y-m-d', strtotime('+7 days')),
1386 + 'amount_due' => '$1,000.00',
1387 + 'due_date' => wp_date('Y-m-d', strtotime('+7 days')),
1527 1388 ];
1528 1389
1529 1390 // Replace placeholders
1530 1391 foreach ($sample_data as $placeholder => $value) {
@@ -1604,16 +1465,36 @@
1604 1465 'quote_number' => 'TEST-Q-001',
1605 1466 'client_name' => 'Test Client',
1606 1467 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'),
1607 1468 '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'),
1469 + 'total_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1470 + 'payment_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1471 + 'due_date' => wp_date('Y-m-d', strtotime('+30 days')),
1472 + 'expiry_date' => wp_date('Y-m-d', strtotime('+30 days')),
1473 + 'payment_date' => current_time('Y-m-d'),
1613 1474 'payment_method' => 'Credit Card',
1614 1475 'transaction_id' => 'TXN-' . uniqid(),
1615 1476 'payment_terms' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
1477 + // Everything the stock templates can mention, so a test never shows a raw tag.
1478 + 'amount_due' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1479 + 'subtotal' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(1000) : '$1,000.00',
1480 + 'tax_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00',
1481 + 'discount_amount' => function_exists('easy_invoice_format_money') ? easy_invoice_format_money(0) : '$0.00',
1482 + 'issue_date' => current_time('Y-m-d'),
1483 + 'invoice_title' => __('Sample invoice', 'easy-invoice'),
1484 + 'quote_title' => __('Sample quote', 'easy-invoice'),
1485 + 'invoice_url' => home_url('/'),
1486 + 'quote_url' => home_url('/'),
1487 + 'payment_url' => home_url('/'),
1488 + 'site_url' => home_url('/'),
1489 + 'admin_url' => admin_url(),
1490 + 'client_email' => 'client@example.com',
1491 + 'client_first_name' => 'Test',
1492 + 'client_last_name' => 'Client',
1493 + 'client_address' => '1 Example Street',
1494 + 'company_phone' => get_option('easy_invoice_company_phone', ''),
1495 + 'company_address' => get_option('easy_invoice_company_address', ''),
1496 + 'company_website' => get_option('easy_invoice_company_website', ''),
1616 1497 ];
1617 1498
1618 1499 // Ensure all sample data values are strings
1619 1500 foreach ($sample_data as $key => $value) {
@@ -1723,9 +1604,9 @@
1723 1604 *
1724 1605 * @return string Quote footer text
1725 1606 */
1726 1607 public static function getQuoteFooterText(): string {
1727 - return get_option('easy_invoice_quote_footer_text', __('Thanks for choosing Easy Invoice', 'easy-invoice'));
1608 + return get_option('easy_invoice_quote_footer_text', __('Thank you for your business.', 'easy-invoice'));
1728 1609 }
1729 1610
1730 1611 /**
1731 1612 * Check if quote accept button should be shown
@@ -1806,41 +1687,9 @@
1806 1687 *
1807 1688 * @return string Invoice email body
1808 1689 */
1809 1690 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'));
1691 + return get_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice'));
1843 1692 }
1844 1693
1845 1694 /**
1846 1695 * Check if quote available email is enabled
@@ -1865,41 +1714,9 @@
1865 1714 *
1866 1715 * @return string Quote email body
1867 1716 */
1868 1717 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'));
1718 + return get_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote'));
1902 1719 }
1903 1720
1904 1721 /**
1905 1722 * Check if payment received email is enabled
@@ -1924,45 +1741,9 @@
1924 1741 *
1925 1742 * @return string Payment email body
1926 1743 */
1927 1744 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'));
1745 + return get_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment'));
1965 1746 }
1966 1747
1967 1748 // ========================================
1968 1749 // TEXT SETTINGS HELPER FUNCTIONS
@@ -1984,11 +1765,74 @@
1984 1765 * @param string $key Text setting key
1985 1766 * @param string $default Default text
1986 1767 * @return string Custom text or default
1987 1768 */
1769 + /**
1770 + * Every document label the site can override, with its saved value.
1771 + *
1772 + * The getText*() methods below are what templates call; this list exists
1773 + * for anything that needs the whole set at once -- the Pro Translations
1774 + * page offers each saved label for translation.
1775 + *
1776 + * @return array<string,array{label:string,default:string,value:string}> keyed by setting key.
1777 + */
1778 + public static function getTextLabels(): array {
1779 + $labels = [
1780 + 'invoice' => __('Invoice', 'easy-invoice'),
1781 + 'invoices' => __('Invoices', 'easy-invoice'),
1782 + 'to' => __('To', 'easy-invoice'),
1783 + 'from' => __('From', 'easy-invoice'),
1784 + 'invoice_number' => __('Invoice Number', 'easy-invoice'),
1785 + 'invoice_date' => __('Invoice Date', 'easy-invoice'),
1786 + 'due_date' => __('Due Date', 'easy-invoice'),
1787 + 'total_due' => __('Total Due', 'easy-invoice'),
1788 + 'qty' => __('Qty', 'easy-invoice'),
1789 + 'service' => __('Service', 'easy-invoice'),
1790 + 'rate_price' => __('Rate', 'easy-invoice'),
1791 + 'adjust' => __('Adjust', 'easy-invoice'),
1792 + 'sub_total' => __('Sub Total', 'easy-invoice'),
1793 + 'total' => __('Total', 'easy-invoice'),
1794 + 'tax' => __('Tax', 'easy-invoice'),
1795 + 'discount' => __('Discount', 'easy-invoice'),
1796 + 'print' => __('Print', 'easy-invoice'),
1797 + 'download_pdf' => __('Download as PDF', 'easy-invoice'),
1798 + 'send_email' => __('Send Email', 'easy-invoice'),
1799 + 'pay_now' => __('Pay Now', 'easy-invoice'),
1800 + 'quote' => __('Quote', 'easy-invoice'),
1801 + 'quote_number' => __('Quote Number', 'easy-invoice'),
1802 + 'quote_date' => __('Quote Date', 'easy-invoice'),
1803 + 'valid_until' => __('Valid Until Date', 'easy-invoice'),
1804 + 'accept_quote' => __('Accept Quote', 'easy-invoice'),
1805 + 'decline_quote' => __('Decline Quote', 'easy-invoice'),
1806 + 'decline_reason' => __('Reason for declining', 'easy-invoice'),
1807 + ];
1808 + $out = [];
1809 + foreach ($labels as $key => $default) {
1810 + $out[$key] = [
1811 + 'label' => ucfirst(str_replace('_', ' ', $key)),
1812 + 'default' => $default,
1813 + 'value' => (string) get_option('easy_invoice_text_' . $key, $default),
1814 + ];
1815 + }
1816 + return $out;
1817 + }
1818 +
1988 1819 public static function getTextSetting(string $key, string $default): string {
1989 1820 $option_key = 'easy_invoice_text_' . $key;
1990 - return get_option($option_key, $default);
1821 + $value = (string) get_option($option_key, $default);
1822 +
1823 + /**
1824 + * Filter a document label after the saved override is applied.
1825 + *
1826 + * Saved labels are literal text and skip gettext, so anything that
1827 + * renders a document in another language (Pro's Client Language) uses
1828 + * this to run them through the active locale.
1829 + *
1830 + * @param string $value Label to show.
1831 + * @param string $key Setting key, e.g. 'total'.
1832 + * @param string $default Translated default.
1833 + */
1834 + return (string) apply_filters('easy_invoice_text_setting', $value, $key, $default);
1991 1835 }
1992 1836
1993 1837 // Invoice Text Settings
1994 1838 public static function getTextInvoice(): string {
@@ -2042,8 +1886,13 @@
2042 1886 public static function getTextTotal(): string {
2043 1887 return self::getTextSetting('total', __('Total', 'easy-invoice'));
2044 1888 }
2045 1889
1890 + /** Heading of the line-item amount column, separate from the grand total. */
1891 + public static function getTextLineTotal(): string {
1892 + return self::getTextSetting('line_total', __('Total', 'easy-invoice'));
1893 + }
1894 +
2046 1895 public static function getTextTax(): string {
2047 1896 return self::getTextSetting('tax', __('Tax', 'easy-invoice'));
2048 1897 }
2049 1898
@@ -2200,8 +2049,9 @@
2200 2049 // Update the next invoice number counter to continue from the last regenerated number + 1
2201 2050 update_option('easy_invoice_next_invoice_number', $current_number);
2202 2051
2203 2052 wp_send_json_success([
2053 + /* translators: %d: number regenerated. */
2204 2054 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count),
2205 2055 'regenerated_count' => $regenerated_count,
2206 2056 'next_number' => $current_number
2207 2057 ]);
@@ -2259,8 +2109,9 @@
2259 2109
2260 2110 update_option('easy_invoice_next_quote_number', $current_number);
2261 2111
2262 2112 wp_send_json_success([
2113 + /* translators: %d: number regenerated. */
2263 2114 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count),
2264 2115 'regenerated_count' => $regenerated_count,
2265 2116 'next_number' => $current_number
2266 2117 ]);