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