| @@ -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,47 +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 | - <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'], | |
| 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'], | |
| 532 | 490 | 'easy_invoice_quote_test_template' => [ |
| 533 | 491 | 'label' => __('Test Quote Email', 'easy-invoice'), |
| 534 | 492 | 'type' => 'test_template_email', |
| 535 | 493 | 'default' => '', |
| @@ -544,45 +502,9 @@ | ||
| 544 | 502 | 'description' => __('Configure email sent when a payment is received', 'easy-invoice'), |
| 545 | 503 | 'fields' => [ |
| 546 | 504 | 'easy_invoice_payment_email_enabled' => ['label' => __('Enable Payment Received Email', 'easy-invoice'), 'type' => 'checkbox', 'default' => 'yes', 'col_span' => 'sm:col-span-6'], |
| 547 | 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'], |
| 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'], | |
| 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'], | |
| 585 | 507 | 'easy_invoice_payment_test_template' => [ |
| 586 | 508 | 'label' => __('Test Payment Email', 'easy-invoice'), |
| 587 | 509 | 'type' => 'test_template_email', |
| 588 | 510 | 'default' => '', |
| @@ -618,8 +540,9 @@ | ||
| 618 | 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')], |
| 619 | 541 | 'easy_invoice_text_adjust' => ['label' => __('Adjust', 'easy-invoice'), 'type' => 'text', 'default' => __('Adjust', 'easy-invoice'), 'col_span' => '', 'description' => __('Adjust label', 'easy-invoice')], |
| 620 | 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')], |
| 621 | 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')], | |
| 622 | 545 | 'easy_invoice_text_tax' => ['label' => __('Tax', 'easy-invoice'), 'type' => 'text', 'default' => __('Tax', 'easy-invoice'), 'col_span' => '', 'description' => __('Tax label', 'easy-invoice')], |
| 623 | 546 | 'easy_invoice_text_discount' => ['label' => __('Discount', 'easy-invoice'), 'type' => 'text', 'default' => __('Discount', 'easy-invoice'), 'col_span' => '', 'description' => __('Discount label', 'easy-invoice')], |
| 624 | 547 | 'easy_invoice_text_print' => ['label' => __('Print', 'easy-invoice'), 'type' => 'text', 'default' => __('Print', 'easy-invoice'), 'col_span' => '', 'description' => __('Print button text', 'easy-invoice')], |
| 625 | 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')], |
| @@ -654,8 +577,9 @@ | ||
| 654 | 577 | ], |
| 655 | 578 | |
| 656 | 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' ], |
| 657 | 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')], | |
| 658 | 582 | ] |
| 659 | 583 | ], |
| 660 | 584 | ]; |
| 661 | 585 | |
| @@ -897,17 +821,21 @@ | ||
| 897 | 821 | * |
| 898 | 822 | * @return void |
| 899 | 823 | */ |
| 900 | 824 | 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')); | |
| 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')); | |
| 904 | 832 | } |
| 905 | 833 | |
| 906 | 834 | |
| 907 | 835 | // Check permissions |
| 908 | 836 | if (!current_user_can('manage_options')) { |
| 909 | - 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')); | |
| 910 | 838 | } |
| 911 | 839 | |
| 912 | 840 | $posted_settings = $_POST['settings'] ?? []; |
| 913 | 841 | |
| @@ -949,8 +877,30 @@ | ||
| 949 | 877 | } |
| 950 | 878 | } |
| 951 | 879 | } |
| 952 | 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 | + | |
| 953 | 903 | // Handle special sections like payment methods |
| 954 | 904 | if (isset($settings_config['payment']) && isset($settings_config['payment']['is_special_section'])) { |
| 955 | 905 | $this->savePaymentSettings($posted_settings, $response); |
| 956 | 906 | } |
| @@ -959,8 +909,9 @@ | ||
| 959 | 909 | wp_cache_flush(); |
| 960 | 910 | |
| 961 | 911 | // Clear settings cache |
| 962 | 912 | $this->settings_cache = null; |
| 913 | + self::$shared_settings_cache = null; | |
| 963 | 914 | |
| 964 | 915 | // Log the save action |
| 965 | 916 | $this->log('Settings saved successfully by user: ' . get_current_user_id()); |
| 966 | 917 | |
| @@ -1073,8 +1024,11 @@ | ||
| 1073 | 1024 | // Return cached settings if available |
| 1074 | 1025 | if ($use_cache && is_array($this->settings_cache)) { |
| 1075 | 1026 | return $this->settings_cache; |
| 1076 | 1027 | } |
| 1028 | + if ($use_cache && is_array(self::$shared_settings_cache)) { | |
| 1029 | + return $this->settings_cache = self::$shared_settings_cache; | |
| 1030 | + } | |
| 1077 | 1031 | |
| 1078 | 1032 | $settings = []; |
| 1079 | 1033 | $config = $this->get_settings_fields_config(); |
| 1080 | 1034 | |
| @@ -1081,8 +1035,15 @@ | ||
| 1081 | 1035 | foreach ($config as $section_id => $section_data) { |
| 1082 | 1036 | if ($section_id === 'payment' && isset($section_data['gateways'])) { |
| 1083 | 1037 | // Handle gateway fields specifically |
| 1084 | 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 | + } | |
| 1085 | 1046 | if (isset($gateway_config['fields']) && is_array($gateway_config['fields'])) { |
| 1086 | 1047 | foreach ($gateway_config['fields'] as $option_key => $field_data) { |
| 1087 | 1048 | $default_value = $field_data['default'] ?? ''; |
| 1088 | 1049 | $settings[$option_key] = get_option($option_key, $default_value); |
| @@ -1144,8 +1105,9 @@ | ||
| 1144 | 1105 | $settings['easy_invoice_payment_gateway_order'] = get_option('easy_invoice_payment_gateway_order', []); |
| 1145 | 1106 | |
| 1146 | 1107 | // Cache settings |
| 1147 | 1108 | $this->settings_cache = $settings; |
| 1109 | + self::$shared_settings_cache = $settings; | |
| 1148 | 1110 | |
| 1149 | 1111 | // Allow third party to add/modify settings |
| 1150 | 1112 | return apply_filters('easy_invoice_settings', $settings); |
| 1151 | 1113 | } |
| @@ -1163,8 +1125,34 @@ | ||
| 1163 | 1125 | /** |
| 1164 | 1126 | * Initialize default settings |
| 1165 | 1127 | */ |
| 1166 | 1128 | public function initializeSettings() { |
| 1129 | + // One-time migration (2.1.22): strip the leftover | |
| 1130 | + // "<strong>Shortcode:</strong> <code>[easy_(invoice|quote)_url …]</code>" | |
| 1131 | + // reference line from saved email bodies. Originally intended as admin-side | |
| 1132 | + // documentation, but it lived inside the email template and got emailed to | |
| 1133 | + // clients verbatim. Guarded by a flag option so each install runs at most once. | |
| 1134 | + // | |
| 1135 | + // The regex tolerates the optional surrounding <p>...</p> because WP's | |
| 1136 | + // wp_kses_post may normalise the wrapping when the option is saved. | |
| 1137 | + if (!get_option('easy_invoice_email_shortcode_hint_removed_v2')) { | |
| 1138 | + foreach (['easy_invoice_invoice_email_body', 'easy_invoice_quote_email_body'] as $opt_key) { | |
| 1139 | + $body = get_option($opt_key, ''); | |
| 1140 | + if (!is_string($body) || $body === '') { | |
| 1141 | + continue; | |
| 1142 | + } | |
| 1143 | + $cleaned = preg_replace( | |
| 1144 | + '#\s*(?:<p>\s*)?<strong>\s*Shortcode\s*:\s*</strong>\s*<code>\s*\[easy_(?:invoice|quote)_url[^\]]*\]\s*</code>(?:\s*</p>)?\s*#i', | |
| 1145 | + '', | |
| 1146 | + $body | |
| 1147 | + ); | |
| 1148 | + if ($cleaned !== null && $cleaned !== $body) { | |
| 1149 | + update_option($opt_key, $cleaned); | |
| 1150 | + } | |
| 1151 | + } | |
| 1152 | + update_option('easy_invoice_email_shortcode_hint_removed_v2', 1); | |
| 1153 | + } | |
| 1154 | + | |
| 1167 | 1155 | // Initialize invoice number settings if not already set |
| 1168 | 1156 | if (!get_option('easy_invoice_invoice_prefix')) { |
| 1169 | 1157 | update_option('easy_invoice_invoice_prefix', 'INV-'); |
| 1170 | 1158 | } |
| @@ -1258,8 +1246,9 @@ | ||
| 1258 | 1246 | 'easy_invoice_text_rate_price' => __('Rate', 'easy-invoice'), |
| 1259 | 1247 | 'easy_invoice_text_adjust' => __('Adjust', 'easy-invoice'), |
| 1260 | 1248 | 'easy_invoice_text_sub_total' => __('Sub Total', 'easy-invoice'), |
| 1261 | 1249 | 'easy_invoice_text_total' => __('Total', 'easy-invoice'), |
| 1250 | + 'easy_invoice_text_line_total' => __('Total', 'easy-invoice'), | |
| 1262 | 1251 | 'easy_invoice_text_tax' => __('Tax', 'easy-invoice'), |
| 1263 | 1252 | 'easy_invoice_text_discount' => __('Discount', 'easy-invoice'), |
| 1264 | 1253 | 'easy_invoice_text_page' => __('Page', 'easy-invoice'), |
| 1265 | 1254 | 'easy_invoice_text_print' => __('Print', 'easy-invoice'), |
| @@ -1299,47 +1288,9 @@ | ||
| 1299 | 1288 | update_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')); |
| 1300 | 1289 | } |
| 1301 | 1290 | |
| 1302 | 1291 | 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')); | |
| 1292 | + update_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice')); | |
| 1342 | 1293 | } |
| 1343 | 1294 | |
| 1344 | 1295 | if (!get_option('easy_invoice_quote_email_enabled')) { |
| 1345 | 1296 | update_option('easy_invoice_quote_email_enabled', 'yes'); |
| @@ -1349,41 +1300,9 @@ | ||
| 1349 | 1300 | update_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')); |
| 1350 | 1301 | } |
| 1351 | 1302 | |
| 1352 | 1303 | 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')); | |
| 1304 | + update_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote')); | |
| 1386 | 1305 | } |
| 1387 | 1306 | |
| 1388 | 1307 | if (!get_option('easy_invoice_payment_email_enabled')) { |
| 1389 | 1308 | update_option('easy_invoice_payment_email_enabled', 'yes'); |
| @@ -1393,45 +1312,9 @@ | ||
| 1393 | 1312 | update_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')); |
| 1394 | 1313 | } |
| 1395 | 1314 | |
| 1396 | 1315 | 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')); | |
| 1316 | + update_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment')); | |
| 1434 | 1317 | } |
| 1435 | 1318 | } |
| 1436 | 1319 | |
| 1437 | 1320 | /** |
| @@ -1485,9 +1368,9 @@ | ||
| 1485 | 1368 | wp_send_json_error(['message' => __('Please provide a valid email address', 'easy-invoice')]); |
| 1486 | 1369 | } |
| 1487 | 1370 | |
| 1488 | 1371 | // Get payment reminder settings |
| 1489 | - $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')); | |
| 1490 | 1373 | $body = get_option('easy_invoice_pro_payment_reminder_body', ''); |
| 1491 | 1374 | |
| 1492 | 1375 | if (empty($body)) { |
| 1493 | 1376 | wp_send_json_error(['message' => __('Payment reminder email template is empty. Please configure the email message first.', 'easy-invoice')]); |
| @@ -1499,9 +1382,10 @@ | ||
| 1499 | 1382 | 'client_name' => 'Test Client', |
| 1500 | 1383 | 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'), |
| 1501 | 1384 | 'company_email' => get_option('easy_invoice_company_email', get_option('admin_email', '')), |
| 1502 | 1385 | 'total_amount' => '$1,000.00', |
| 1503 | - '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')), | |
| 1504 | 1388 | ]; |
| 1505 | 1389 | |
| 1506 | 1390 | // Replace placeholders |
| 1507 | 1391 | foreach ($sample_data as $placeholder => $value) { |
| @@ -1581,16 +1465,36 @@ | ||
| 1581 | 1465 | 'quote_number' => 'TEST-Q-001', |
| 1582 | 1466 | 'client_name' => 'Test Client', |
| 1583 | 1467 | 'company_name' => get_option('easy_invoice_company_name', get_bloginfo('name') ?: 'Easy Invoice'), |
| 1584 | 1468 | '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'), | |
| 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'), | |
| 1590 | 1474 | 'payment_method' => 'Credit Card', |
| 1591 | 1475 | 'transaction_id' => 'TXN-' . uniqid(), |
| 1592 | 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', ''), | |
| 1593 | 1497 | ]; |
| 1594 | 1498 | |
| 1595 | 1499 | // Ensure all sample data values are strings |
| 1596 | 1500 | foreach ($sample_data as $key => $value) { |
| @@ -1700,9 +1604,9 @@ | ||
| 1700 | 1604 | * |
| 1701 | 1605 | * @return string Quote footer text |
| 1702 | 1606 | */ |
| 1703 | 1607 | public static function getQuoteFooterText(): string { |
| 1704 | - 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')); | |
| 1705 | 1609 | } |
| 1706 | 1610 | |
| 1707 | 1611 | /** |
| 1708 | 1612 | * Check if quote accept button should be shown |
| @@ -1783,41 +1687,9 @@ | ||
| 1783 | 1687 | * |
| 1784 | 1688 | * @return string Invoice email body |
| 1785 | 1689 | */ |
| 1786 | 1690 | 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')); | |
| 1691 | + return get_option('easy_invoice_invoice_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('invoice')); | |
| 1820 | 1692 | } |
| 1821 | 1693 | |
| 1822 | 1694 | /** |
| 1823 | 1695 | * Check if quote available email is enabled |
| @@ -1842,41 +1714,9 @@ | ||
| 1842 | 1714 | * |
| 1843 | 1715 | * @return string Quote email body |
| 1844 | 1716 | */ |
| 1845 | 1717 | 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')); | |
| 1718 | + return get_option('easy_invoice_quote_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('quote')); | |
| 1879 | 1719 | } |
| 1880 | 1720 | |
| 1881 | 1721 | /** |
| 1882 | 1722 | * Check if payment received email is enabled |
| @@ -1901,45 +1741,9 @@ | ||
| 1901 | 1741 | * |
| 1902 | 1742 | * @return string Payment email body |
| 1903 | 1743 | */ |
| 1904 | 1744 | 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')); | |
| 1745 | + return get_option('easy_invoice_payment_email_body', \EasyInvoice\Services\EmailManager::defaultTemplate('payment')); | |
| 1942 | 1746 | } |
| 1943 | 1747 | |
| 1944 | 1748 | // ======================================== |
| 1945 | 1749 | // TEXT SETTINGS HELPER FUNCTIONS |
| @@ -1961,11 +1765,74 @@ | ||
| 1961 | 1765 | * @param string $key Text setting key |
| 1962 | 1766 | * @param string $default Default text |
| 1963 | 1767 | * @return string Custom text or default |
| 1964 | 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 | + | |
| 1965 | 1819 | public static function getTextSetting(string $key, string $default): string { |
| 1966 | 1820 | $option_key = 'easy_invoice_text_' . $key; |
| 1967 | - 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); | |
| 1968 | 1835 | } |
| 1969 | 1836 | |
| 1970 | 1837 | // Invoice Text Settings |
| 1971 | 1838 | public static function getTextInvoice(): string { |
| @@ -2019,8 +1886,13 @@ | ||
| 2019 | 1886 | public static function getTextTotal(): string { |
| 2020 | 1887 | return self::getTextSetting('total', __('Total', 'easy-invoice')); |
| 2021 | 1888 | } |
| 2022 | 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 | + | |
| 2023 | 1895 | public static function getTextTax(): string { |
| 2024 | 1896 | return self::getTextSetting('tax', __('Tax', 'easy-invoice')); |
| 2025 | 1897 | } |
| 2026 | 1898 | |
| @@ -2177,8 +2049,9 @@ | ||
| 2177 | 2049 | // Update the next invoice number counter to continue from the last regenerated number + 1 |
| 2178 | 2050 | update_option('easy_invoice_next_invoice_number', $current_number); |
| 2179 | 2051 | |
| 2180 | 2052 | wp_send_json_success([ |
| 2053 | + /* translators: %d: number regenerated. */ | |
| 2181 | 2054 | 'message' => sprintf(__('Successfully regenerated %d invoice numbers', 'easy-invoice'), $regenerated_count), |
| 2182 | 2055 | 'regenerated_count' => $regenerated_count, |
| 2183 | 2056 | 'next_number' => $current_number |
| 2184 | 2057 | ]); |
| @@ -2236,8 +2109,9 @@ | ||
| 2236 | 2109 | |
| 2237 | 2110 | update_option('easy_invoice_next_quote_number', $current_number); |
| 2238 | 2111 | |
| 2239 | 2112 | wp_send_json_success([ |
| 2113 | + /* translators: %d: number regenerated. */ | |
| 2240 | 2114 | 'message' => sprintf(__('Successfully regenerated %d quote numbers', 'easy-invoice'), $regenerated_count), |
| 2241 | 2115 | 'regenerated_count' => $regenerated_count, |
| 2242 | 2116 | 'next_number' => $current_number |
| 2243 | 2117 | ]); |