| @@ -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 | ]); |