Changeset 3393362
- Timestamp:
- 11/11/2025 04:55:44 AM (4 months ago)
- Location:
- easy-invoice
- Files:
-
- 26 edited
- 1 copied
-
tags/2.1.4 (copied) (copied from easy-invoice/trunk)
-
tags/2.1.4/easy-invoice.php (modified) (2 diffs)
-
tags/2.1.4/includes/Admin/AdminController.php (modified) (1 diff)
-
tags/2.1.4/includes/Controllers/PaymentController.php (modified) (5 diffs)
-
tags/2.1.4/includes/Controllers/QuoteController.php (modified) (2 diffs)
-
tags/2.1.4/includes/Forms/FormProcessor.php (modified) (8 diffs)
-
tags/2.1.4/includes/Gateways/PayPalGateway.php (modified) (2 diffs)
-
tags/2.1.4/includes/Models/Invoice.php (modified) (1 diff)
-
tags/2.1.4/includes/Models/Quote.php (modified) (1 diff)
-
tags/2.1.4/includes/Repositories/InvoiceRepository.php (modified) (1 diff)
-
tags/2.1.4/includes/Repositories/QuoteRepository.php (modified) (1 diff)
-
tags/2.1.4/includes/Services/EmailManager.php (modified) (2 diffs)
-
tags/2.1.4/includes/Traits/PaymentCalculationTrait.php (modified) (1 diff)
-
tags/2.1.4/readme.txt (modified) (2 diffs)
-
trunk/easy-invoice.php (modified) (2 diffs)
-
trunk/includes/Admin/AdminController.php (modified) (1 diff)
-
trunk/includes/Controllers/PaymentController.php (modified) (5 diffs)
-
trunk/includes/Controllers/QuoteController.php (modified) (2 diffs)
-
trunk/includes/Forms/FormProcessor.php (modified) (8 diffs)
-
trunk/includes/Gateways/PayPalGateway.php (modified) (2 diffs)
-
trunk/includes/Models/Invoice.php (modified) (1 diff)
-
trunk/includes/Models/Quote.php (modified) (1 diff)
-
trunk/includes/Repositories/InvoiceRepository.php (modified) (1 diff)
-
trunk/includes/Repositories/QuoteRepository.php (modified) (1 diff)
-
trunk/includes/Services/EmailManager.php (modified) (2 diffs)
-
trunk/includes/Traits/PaymentCalculationTrait.php (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
easy-invoice/tags/2.1.4/easy-invoice.php
r3388559 r3393362 4 4 * Plugin URI: https://matrixaddons.com/plugins/easy-invoice 5 5 * Description: A beautiful, full-featured invoicing solution for WordPress. Create professional invoices, quotes, and manage payments with ease. 6 * Version: 2.1. 36 * Version: 2.1.4 7 7 * Author: MatrixAddons 8 8 * Author URI: https://matrixaddons.com … … 25 25 26 26 // Define plugin constants. 27 define( 'EASY_INVOICE_VERSION', '2.1. 3' );27 define( 'EASY_INVOICE_VERSION', '2.1.4' ); 28 28 define( 'EASY_INVOICE_PLUGIN_FILE', __FILE__ ); 29 29 define( 'EASY_INVOICE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
easy-invoice/tags/2.1.4/includes/Admin/AdminController.php
r3345595 r3393362 38 38 */ 39 39 public function sendManualPaymentNotification($invoice_id, $payment_method) { 40 // Get admin email41 $admin_email = get_option('admin_email');42 43 40 // Get invoice details 44 41 $invoice = new \EasyInvoice\Models\Invoice(get_post($invoice_id)); 45 42 46 // Get customer details47 $customer_name = $invoice->getCustomerName();48 $customer_email = $invoice->getCustomerEmail();43 if (!$invoice || !$invoice->getId()) { 44 return; 45 } 49 46 50 // Format amount 51 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice); 52 $amount = $formatter->format($invoice->getTotal()); 53 54 // Format payment method 55 $payment_method_label = $payment_method === 'bank' ? 'Bank Transfer' : 'Cheque'; 56 57 // Email subject 58 $subject = sprintf( 59 __('New %s Payment Received - Invoice #%s', 'easy-invoice'), 60 $payment_method_label, 61 $invoice->getNumber() 62 ); 63 64 // Email message 65 $message = sprintf( 66 __('A new %s payment has been submitted for invoice #%s. 67 68 Invoice Details: 69 - Amount: %s 70 - Customer: %s 71 - Email: %s 72 73 Please review and verify this payment in the admin dashboard: 74 %s 75 76 This is an automated message from Easy Invoice.', 'easy-invoice'), 77 $payment_method_label, 78 $invoice->getNumber(), 79 $amount, 80 $customer_name, 81 $customer_email, 82 admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id) 83 ); 84 85 // Send email 86 wp_mail($admin_email, $subject, $message); 47 // Use EmailManager to send admin notification 48 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 49 $email_manager->sendAdminPaymentNotification($invoice, [ 50 'payment_method' => $payment_method 51 ]); 87 52 } 88 53 -
easy-invoice/tags/2.1.4/includes/Controllers/PaymentController.php
r3346980 r3393362 694 694 $payment = Payment::create($payment_data); 695 695 696 // Store payment details before updating status (for the hook) 697 $invoice->setMeta('_payment_method', $payment_method); 698 if ($transaction_id) { 699 $invoice->setMeta('_transaction_id', $transaction_id); 700 } 701 696 702 // Update invoice status to paid only if total payments are sufficient 703 // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification 697 704 $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'manual'); 698 705 … … 765 772 private function sendPaymentConfirmationEmail($invoice_id, $payment_id): void { 766 773 $invoice = new Invoice(get_post($invoice_id)); 767 $customer_email = $invoice->getCustomerEmail(); 768 769 if (!$customer_email) { 770 return; 771 } 772 773 // Get currency settings 774 $settings_controller = new \EasyInvoice\Controllers\SettingsController(); 775 $settings = $settings_controller->getSettings(); 776 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; 777 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); 778 779 $site_name = get_bloginfo('name'); 780 $invoice_number = $invoice->getNumber(); 781 $amount = $invoice->getTotal(); 782 $formatted_amount = $currency_symbol . number_format($amount, 2); 783 784 $subject = sprintf(__('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number); 785 786 $message = sprintf( 787 __('Dear %s,', 'easy-invoice'), 788 $invoice->getCustomerName() 789 ); 790 $message .= "\n\n"; 791 $message .= sprintf( 792 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'), 793 $formatted_amount, 794 $invoice_number 795 ); 796 $message .= "\n\n"; 797 $message .= __('Thank you for your business.', 'easy-invoice'); 798 $message .= "\n\n"; 799 $message .= sprintf(__('Regards,', 'easy-invoice')); 800 $message .= "\n"; 801 $message .= get_option('easy_invoice_company_name', $site_name); 802 803 wp_mail($customer_email, $subject, $message); 774 775 if (!$invoice || !$invoice->getId()) { 776 return; 777 } 778 779 // Use EmailManager to send payment confirmation 780 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 781 $email_manager->sendPaymentConfirmationEmail($invoice, [ 782 'payment_id' => $payment_id 783 ]); 804 784 } 805 785 … … 812 792 private function sendPaymentRejectionEmail($invoice_id, $reason): void { 813 793 $invoice = new Invoice(get_post($invoice_id)); 814 $customer_email = $invoice->getCustomerEmail(); 815 816 if (!$customer_email) { 817 return; 818 } 819 820 // Get currency settings 821 $settings_controller = new \EasyInvoice\Controllers\SettingsController(); 822 $settings = $settings_controller->getSettings(); 823 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; 824 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); 825 826 $site_name = get_bloginfo('name'); 827 $invoice_number = $invoice->getNumber(); 828 $amount = $invoice->getTotal(); 829 $formatted_amount = $currency_symbol . number_format($amount, 2); 830 831 $subject = sprintf(__('[%s] Payment Issue - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number); 832 833 $message = sprintf( 834 __('Dear %s,', 'easy-invoice'), 835 $invoice->getCustomerName() 836 ); 837 $message .= "\n\n"; 838 $message .= sprintf( 839 __('We regret to inform you that we could not process your payment of %s for Invoice #%s.', 'easy-invoice'), 840 $formatted_amount, 841 $invoice_number 842 ); 843 $message .= "\n\n"; 844 845 if ($reason) { 846 $message .= __('Reason:', 'easy-invoice') . "\n"; 847 $message .= $reason; 848 $message .= "\n\n"; 849 } 850 851 $message .= __('Please contact us to arrange an alternative payment method.', 'easy-invoice'); 852 $message .= "\n\n"; 853 $message .= sprintf(__('Regards,', 'easy-invoice')); 854 $message .= "\n"; 855 $message .= get_option('easy_invoice_company_name', $site_name); 856 857 wp_mail($customer_email, $subject, $message); 794 795 if (!$invoice || !$invoice->getId()) { 796 return; 797 } 798 799 // Use EmailManager to send payment rejection 800 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 801 $email_manager->sendPaymentRejectionEmail($invoice, $reason); 858 802 } 859 803 … … 938 882 foreach ($pending_invoices as $post) { 939 883 $invoice = new Invoice($post); 940 $customer_email = $invoice->getCustomerEmail(); 941 942 if (!$customer_email) { 884 885 if (!$invoice || !$invoice->getId()) { 943 886 continue; 944 887 } 945 888 946 $site_name = get_bloginfo('name'); 947 $invoice_number = $invoice->getNumber(); 948 $amount = $invoice->getTotal(); 949 $formatted_amount = $currency_symbol . number_format($amount, 2); 950 $payment_method = get_post_meta($invoice->getId(), '_payment_method', true); 951 $payment_method_label = $payment_method === 'bank' ? __('Bank Transfer', 'easy-invoice') : __('Cheque', 'easy-invoice'); 952 953 $subject = sprintf(__('[%s] Payment Reminder - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number); 954 955 $message = sprintf( 956 __('Dear %s,', 'easy-invoice'), 957 $invoice->getCustomerName() 958 ); 959 $message .= "\n\n"; 960 $message .= sprintf( 961 __('This is a friendly reminder that we are still awaiting your %s payment of %s for Invoice #%s.', 'easy-invoice'), 962 $payment_method_label, 963 $formatted_amount, 964 $invoice_number 965 ); 966 $message .= "\n\n"; 967 $message .= __('If you have already sent the payment, please disregard this reminder. If not, please arrange for payment at your earliest convenience.', 'easy-invoice'); 968 $message .= "\n\n"; 969 $message .= sprintf(__('Regards,', 'easy-invoice')); 970 $message .= "\n"; 971 $message .= get_option('easy_invoice_company_name', $site_name); 972 973 wp_mail($customer_email, $subject, $message); 974 975 // Mark reminder as sent 976 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql')); 889 // Use EmailManager to send payment reminder 890 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 891 $result = $email_manager->sendInvoiceEmail($invoice, 'reminder', [ 892 'payment_method' => get_post_meta($invoice->getId(), '_payment_method', true) 893 ]); 894 895 // Mark reminder as sent if email was sent successfully 896 if ($result['success']) { 897 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql')); 898 } 977 899 } 978 900 … … 1188 1110 } 1189 1111 1112 // Store payment details before updating status (for the hook) 1113 $transaction_id = get_post_meta($invoice_id, '_' . $payment_method . '_transaction_id', true) ?: 'MANUAL-' . $invoice_id; 1114 $invoice->setMeta('_payment_method', $payment_method); 1115 $invoice->setMeta('_transaction_id', $transaction_id); 1116 1117 // Update invoice status to paid 1118 // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification 1119 $invoice->setStatus('paid'); 1120 $invoice->save(); 1121 1122 // Trigger the payment completed hook manually since we're updating status directly 1123 do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [ 1124 'payment_method' => $payment_method, 1125 'gateway_name' => 'manual', 1126 'transaction_id' => $transaction_id, 1127 'amount' => $invoice->getTotal() 1128 ]); 1129 1190 1130 // Trigger email confirmation and actions only if we have a payment_id 1191 1131 if ($payment_id) { 1132 // Send confirmation email to customer 1192 1133 $this->sendPaymentConfirmationEmail($invoice_id, $payment_id); 1193 1134 do_action('easy_invoice_manual_payment_confirmed', $invoice_id, $payment_id, $payment_method); -
easy-invoice/tags/2.1.4/includes/Controllers/QuoteController.php
r3387040 r3393362 1349 1349 */ 1350 1350 private function sendQuoteAcceptanceNotification($quote): void { 1351 $admin_email = get_option('admin_email'); 1352 $site_name = get_bloginfo('name'); 1353 1354 $subject = sprintf(__('Quote %s has been accepted', 'easy-invoice'), $quote->getNumber()); 1355 1356 $message = sprintf( 1357 __('Hello, 1358 1359 The quote %s for %s has been accepted by the client. 1360 1361 Quote Details: 1362 - Quote Number: %s 1363 - Client: %s 1364 - Total Amount: %s 1365 - Accepted Date: %s 1366 1367 You can view the quote at: %s 1368 1369 Best regards, 1370 %s', 'easy-invoice'), 1371 $quote->getNumber(), 1372 $quote->getCustomerName(), 1373 $quote->getNumber(), 1374 $quote->getCustomerName(), 1375 $this->formatCurrency($quote->getTotal(), $quote), 1376 date_i18n(get_option('date_format') . ' ' . get_option('time_format')), 1377 get_permalink($quote->getId()), 1378 $site_name 1379 ); 1380 1381 wp_mail($admin_email, $subject, $message); 1351 // Use EmailManager to send admin notification 1352 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 1353 $email_manager->sendAdminQuoteNotification($quote, 'accepted'); 1382 1354 } 1383 1355 … … 1388 1360 */ 1389 1361 private function sendQuoteDeclineNotification($quote): void { 1390 $admin_email = get_option('admin_email'); 1391 $site_name = get_bloginfo('name'); 1392 1393 $subject = sprintf(__('Quote %s has been declined', 'easy-invoice'), $quote->getNumber()); 1394 1395 $message = sprintf( 1396 __('Hello, 1397 1398 The quote %s for %s has been declined by the client. 1399 1400 Quote Details: 1401 - Quote Number: %s 1402 - Client: %s 1403 - Total Amount: %s 1404 - Declined Date: %s 1405 1406 You can view the quote at: %s 1407 1408 Best regards, 1409 %s', 'easy-invoice'), 1410 $quote->getNumber(), 1411 $quote->getCustomerName(), 1412 $quote->getNumber(), 1413 $quote->getCustomerName(), 1414 $this->formatCurrency($quote->getTotal(), $quote), 1415 date_i18n(get_option('date_format') . ' ' . get_option('time_format')), 1416 get_permalink($quote->getId()), 1417 $site_name 1418 ); 1419 1420 wp_mail($admin_email, $subject, $message); 1362 // Use EmailManager to send admin notification 1363 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 1364 $email_manager->sendAdminQuoteNotification($quote, 'declined'); 1421 1365 } 1422 1366 -
easy-invoice/tags/2.1.4/includes/Forms/FormProcessor.php
r3348168 r3393362 23 23 24 24 /** 25 * Allowed HTML tags for textarea fields 26 * 27 * @since 1.0.0 28 * @var array 29 */ 30 private static $allowed_textarea_tags = [ 31 'p' => [], 32 'br' => [], 33 'strong' => [], 34 'em' => [], 35 'i' => [], 36 'b' => [], 37 'u' => [], 38 'ul' => [], 39 'ol' => [], 40 'li' => [], 41 'h1' => [], 42 'h2' => [], 43 'h3' => [], 44 'h4' => [], 45 'h5' => [], 46 'h6' => [], 47 'a' => [ 48 'href' => [], 49 'title' => [], 50 'target' => [], 51 ], 52 'span' => [], 53 'div' => [], 54 ]; 55 56 /** 57 * Sanitize textarea field allowing basic HTML tags 58 * 59 * @since 1.0.0 60 * @param string $value The value to sanitize 61 * @return string Sanitized value with allowed HTML tags 62 */ 63 public static function sanitizeTextareaWithHtml(string $value): string { 64 // Use wp_kses to allow only safe HTML tags 65 return wp_kses($value, self::$allowed_textarea_tags); 66 } 67 68 /** 25 69 * Process form data 26 70 * … … 52 96 } 53 97 $required = $field['required'] ?? false; 98 $field_type = $field['type'] ?? 'text'; 54 99 $raw_value = $raw_data[$field_name] ?? ''; 55 100 … … 63 108 } 64 109 65 // Skip empty non-required fields 66 if (empty($raw_value) && !$required && $raw_value !== '0') { 110 // Always include textarea fields (like description) even when empty 111 // This allows users to clear these fields by submitting empty values 112 $always_include_fields = ['description', 'notes', 'terms', 'internal_notes']; 113 $should_always_include = in_array($field_name, $always_include_fields) || $field_type === 'textarea'; 114 115 // Skip empty non-required fields (unless they should always be included) 116 if (empty($raw_value) && !$required && $raw_value !== '0' && !$should_always_include) { 67 117 continue; 68 118 } … … 208 258 $value = $form_data[$field_name] ?? null; 209 259 210 211 212 // Only save if value exists and is not empty (or is 0) 213 if ($value !== null && $value !== '') { 260 // Fields that should always be saved, even if empty (to allow clearing) 261 $always_save_fields = ['description', 'notes', 'terms', 'internal_notes']; 262 $should_always_save = in_array($field_name, $always_save_fields); 263 264 // Save if value exists and is not empty (or is 0), OR if it's a field that should always be saved 265 if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) { 214 266 // Use custom save callback if provided 215 267 if (isset($field['save_callback']) && is_callable($field['save_callback'])) { 216 $field['save_callback']($value , $model);268 $field['save_callback']($value ?? '', $model); 217 269 } else { 218 270 // Default save to meta data 219 271 if (method_exists($model, 'setMetaData')) { 220 $model->setMetaData($db_key, $value); 221 222 272 $model->setMetaData($db_key, $value ?? ''); 223 273 } 224 274 } … … 227 277 228 278 // Also process any extra fields that might not be in the configuration 279 $always_save_fields = ['description', 'notes', 'terms', 'internal_notes']; 229 280 foreach ($form_data as $field_name => $value) { 230 if ($value !== null && $value !== '') { 281 $should_always_save = in_array($field_name, $always_save_fields); 282 283 if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) { 231 284 $db_key = '_easy_invoice_' . $field_name; 232 285 … … 241 294 242 295 if (!$already_processed && method_exists($model, 'setMetaData')) { 243 $model->setMetaData($db_key, $value );296 $model->setMetaData($db_key, $value ?? ''); 244 297 } 245 298 } … … 293 346 294 347 case 'textarea': 295 return sanitize_textarea_field($value); 348 // Allow basic HTML tags in textarea fields 349 return self::sanitizeTextareaWithHtml($value); 296 350 297 351 case 'email': … … 324 378 325 379 if (is_callable($sanitize_callback)) { 380 // If callback is 'sanitize_textarea_field', use our HTML-allowing version for textarea fields 381 if ($sanitize_callback === 'sanitize_textarea_field' && ($field['type'] ?? '') === 'textarea') { 382 return self::sanitizeTextareaWithHtml($value); 383 } 326 384 return $sanitize_callback($value); 327 385 } -
easy-invoice/tags/2.1.4/includes/Gateways/PayPalGateway.php
r3346980 r3393362 539 539 540 540 // Update invoice status to paid only if total payments are sufficient 541 // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification 541 542 $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'paypal'); 542 543 … … 546 547 wp_update_post(['ID' => $invoice_id, 'post_status' => 'publish']); 547 548 } 549 550 // Store payment details for the hook 551 $invoice->setMeta('_payment_method', 'paypal'); 552 $invoice->setMeta('_transaction_id', $paypal_txn_id); 548 553 549 554 return [ -
easy-invoice/tags/2.1.4/includes/Models/Invoice.php
r3348168 r3393362 446 446 447 447 // Get the value from dynamic data array 448 $value = $this->data[$field_name] ?? null; 449 if ($value !== null) { 448 // Save all fields that exist in the data array (including empty strings to allow clearing fields) 449 // If a field exists in $this->data, it means it was explicitly set, so we should save it 450 if (array_key_exists($field_name, $this->data)) { 451 $value = $this->data[$field_name]; 450 452 update_post_meta($this->id, $meta_key, $value); 451 453 } -
easy-invoice/tags/2.1.4/includes/Models/Quote.php
r3363158 r3393362 486 486 487 487 // Get the value from dynamic data array 488 $value = $this->data[$field_name] ?? null;489 if ($value !== null) {490 491 488 // Save all fields that exist in the data array (including empty strings to allow clearing fields) 489 // If a field exists in $this->data, it means it was explicitly set, so we should save it 490 if (array_key_exists($field_name, $this->data)) { 491 $value = $this->data[$field_name]; 492 492 update_post_meta($this->id, $meta_key, $value); 493 494 // Debug: Log template field saving to database495 496 493 } 497 494 } -
easy-invoice/tags/2.1.4/includes/Repositories/InvoiceRepository.php
r3387051 r3393362 397 397 } 398 398 399 if (isset($data['description'])) { 400 $invoice->setDescription($data['description']); 401 } 402 403 if (isset($data['invoice_description'])) { 404 $invoice->setDescription($data['invoice_description']); 399 // Always update description if it exists in data (even if empty, to allow clearing) 400 if (array_key_exists('description', $data)) { 401 $invoice->setDescription($data['description'] ?? ''); 402 } 403 404 if (array_key_exists('invoice_description', $data)) { 405 $invoice->setDescription($data['invoice_description'] ?? ''); 405 406 } 406 407 -
easy-invoice/tags/2.1.4/includes/Repositories/QuoteRepository.php
r3387051 r3393362 495 495 } 496 496 497 if (isset($data['description'])) { 498 $quote->setDescription($data['description']); 497 // Always update description if it exists in data (even if empty, to allow clearing) 498 if (array_key_exists('description', $data)) { 499 $quote->setDescription($data['description'] ?? ''); 499 500 } 500 501 -
easy-invoice/tags/2.1.4/includes/Services/EmailManager.php
r3387040 r3393362 109 109 add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3); 110 110 add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3); 111 112 // Listen for payment completion to send admin notifications 113 add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3); 111 114 } 112 115 … … 1467 1470 } 1468 1471 1472 /** 1473 * Send admin notification when payment is received 1474 * 1475 * @param Invoice $invoice The invoice 1476 * @param array $payment_data Payment data (method, amount, etc.) 1477 * @return array Result array with success status and message 1478 */ 1479 public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array { 1480 try { 1481 // Validate invoice 1482 if (!$invoice || !$invoice->getId()) { 1483 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; 1484 } 1485 1486 // Get admin email 1487 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email'); 1488 if (empty($admin_email)) { 1489 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')]; 1490 } 1491 1492 // Get payment method 1493 $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online'; 1494 $payment_method_label = $this->getPaymentMethodLabel($payment_method); 1495 1496 // Format amount 1497 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice); 1498 $amount = $formatter->format($invoice->getTotal()); 1499 1500 // Prepare email subject 1501 $subject = sprintf( 1502 __('New Payment Received - Invoice #%s', 'easy-invoice'), 1503 $invoice->getNumber() 1504 ); 1505 1506 // Prepare email message 1507 $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data); 1508 1509 // Add HTML wrapper if enabled 1510 if ($this->settings['enable_html'] === 'yes') { 1511 $message = $this->wrapInHtmlTemplate($message); 1512 } 1513 1514 // Prepare headers 1515 $headers = $this->prepareEmailHeaders(); 1516 1517 // Send email 1518 $sent = $this->sendEmail($admin_email, $subject, $message, $headers); 1519 1520 if ($sent) { 1521 do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data); 1522 return [ 1523 'success' => true, 1524 'message' => __('Admin notification sent successfully', 'easy-invoice') 1525 ]; 1526 } else { 1527 do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data); 1528 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')]; 1529 } 1530 1531 } catch (\Exception $e) { 1532 $this->log('Admin payment notification error: ' . $e->getMessage(), 'error'); 1533 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()]; 1534 } 1535 } 1536 1537 /** 1538 * Send payment confirmation email to customer 1539 * 1540 * @param Invoice $invoice The invoice 1541 * @param array $payment_data Payment data 1542 * @return array Result array with success status and message 1543 */ 1544 public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array { 1545 try { 1546 // Validate invoice 1547 if (!$invoice || !$invoice->getId()) { 1548 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; 1549 } 1550 1551 // Get customer email 1552 $customer_email = $invoice->getCustomerEmail(); 1553 if (empty($customer_email)) { 1554 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')]; 1555 } 1556 1557 // Get currency settings 1558 $settings_controller = new \EasyInvoice\Controllers\SettingsController(); 1559 $settings = $settings_controller->getSettings(); 1560 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; 1561 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); 1562 1563 // Format amount 1564 $amount = $invoice->getTotal(); 1565 $formatted_amount = $currency_symbol . number_format($amount, 2); 1566 1567 // Prepare email subject 1568 $site_name = get_bloginfo('name'); 1569 $subject = sprintf( 1570 __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), 1571 $site_name, 1572 $invoice->getNumber() 1573 ); 1574 1575 // Prepare email message 1576 $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount); 1577 1578 // Add HTML wrapper if enabled 1579 if ($this->settings['enable_html'] === 'yes') { 1580 $message = $this->wrapInHtmlTemplate($message); 1581 } 1582 1583 // Prepare headers 1584 $headers = $this->prepareEmailHeaders(); 1585 1586 // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate) 1587 // The payment completion hook already sends a dedicated admin notification 1588 $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true; 1589 if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { 1590 $headers[] = 'Bcc: ' . $this->settings['admin_email']; 1591 } 1592 1593 // Send email 1594 $sent = $this->sendEmail($customer_email, $subject, $message, $headers); 1595 1596 if ($sent) { 1597 do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data); 1598 return [ 1599 'success' => true, 1600 'message' => __('Payment confirmation email sent successfully', 'easy-invoice') 1601 ]; 1602 } else { 1603 do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data); 1604 return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')]; 1605 } 1606 1607 } catch (\Exception $e) { 1608 $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error'); 1609 return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()]; 1610 } 1611 } 1612 1613 /** 1614 * Send payment rejection email to customer 1615 * 1616 * @param Invoice $invoice The invoice 1617 * @param string $reason Rejection reason 1618 * @return array Result array with success status and message 1619 */ 1620 public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array { 1621 try { 1622 // Validate invoice 1623 if (!$invoice || !$invoice->getId()) { 1624 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; 1625 } 1626 1627 // Get customer email 1628 $customer_email = $invoice->getCustomerEmail(); 1629 if (empty($customer_email)) { 1630 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')]; 1631 } 1632 1633 // Prepare email subject 1634 $site_name = get_bloginfo('name'); 1635 $subject = sprintf( 1636 __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'), 1637 $site_name, 1638 $invoice->getNumber() 1639 ); 1640 1641 // Prepare email message 1642 $message = $this->preparePaymentRejectionMessage($invoice, $reason); 1643 1644 // Add HTML wrapper if enabled 1645 if ($this->settings['enable_html'] === 'yes') { 1646 $message = $this->wrapInHtmlTemplate($message); 1647 } 1648 1649 // Prepare headers 1650 $headers = $this->prepareEmailHeaders(); 1651 1652 // Add BCC to admin if enabled 1653 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { 1654 $headers[] = 'Bcc: ' . $this->settings['admin_email']; 1655 } 1656 1657 // Send email 1658 $sent = $this->sendEmail($customer_email, $subject, $message, $headers); 1659 1660 if ($sent) { 1661 do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason); 1662 return [ 1663 'success' => true, 1664 'message' => __('Payment rejection email sent successfully', 'easy-invoice') 1665 ]; 1666 } else { 1667 do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason); 1668 return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')]; 1669 } 1670 1671 } catch (\Exception $e) { 1672 $this->log('Payment rejection email error: ' . $e->getMessage(), 'error'); 1673 return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()]; 1674 } 1675 } 1676 1677 /** 1678 * Prepare admin payment notification message 1679 * 1680 * @param Invoice $invoice The invoice 1681 * @param string $payment_method_label Payment method label 1682 * @param string $amount Formatted amount 1683 * @param array $payment_data Payment data 1684 * @return string Email message 1685 */ 1686 private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string { 1687 $invoice_number = $invoice->getNumber(); 1688 $customer_name = $invoice->getCustomerName(); 1689 $customer_email = $invoice->getCustomerEmail(); 1690 $invoice_id = $invoice->getId(); 1691 1692 $message = sprintf( 1693 __('A new %s payment has been received for invoice #%s.', 'easy-invoice'), 1694 $payment_method_label, 1695 $invoice_number 1696 ); 1697 $message .= "\n\n"; 1698 $message .= __('Invoice Details:', 'easy-invoice'); 1699 $message .= "\n"; 1700 $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount); 1701 $message .= "\n"; 1702 $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name); 1703 $message .= "\n"; 1704 $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email); 1705 1706 // Add transaction ID if available 1707 if (!empty($payment_data['transaction_id'])) { 1708 $message .= "\n"; 1709 $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']); 1710 } 1711 1712 $message .= "\n\n"; 1713 $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice'); 1714 $message .= "\n"; 1715 $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id); 1716 $message .= "\n\n"; 1717 $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice'); 1718 1719 return $message; 1720 } 1721 1722 /** 1723 * Prepare payment confirmation message 1724 * 1725 * @param Invoice $invoice The invoice 1726 * @param string $formatted_amount Formatted amount 1727 * @return string Email message 1728 */ 1729 private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string { 1730 $customer_name = $invoice->getCustomerName(); 1731 $invoice_number = $invoice->getNumber(); 1732 $site_name = get_bloginfo('name'); 1733 $company_name = get_option('easy_invoice_company_name', $site_name); 1734 1735 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name); 1736 $message .= "\n\n"; 1737 $message .= sprintf( 1738 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'), 1739 $formatted_amount, 1740 $invoice_number 1741 ); 1742 $message .= "\n\n"; 1743 $message .= __('Thank you for your business.', 'easy-invoice'); 1744 $message .= "\n\n"; 1745 $message .= __('Regards,', 'easy-invoice'); 1746 $message .= "\n"; 1747 $message .= $company_name; 1748 1749 return $message; 1750 } 1751 1752 /** 1753 * Prepare payment rejection message 1754 * 1755 * @param Invoice $invoice The invoice 1756 * @param string $reason Rejection reason 1757 * @return string Email message 1758 */ 1759 private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string { 1760 $customer_name = $invoice->getCustomerName(); 1761 $invoice_number = $invoice->getNumber(); 1762 $site_name = get_bloginfo('name'); 1763 $company_name = get_option('easy_invoice_company_name', $site_name); 1764 1765 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name); 1766 $message .= "\n\n"; 1767 $message .= sprintf( 1768 __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'), 1769 $invoice_number 1770 ); 1771 1772 if (!empty($reason)) { 1773 $message .= "\n\n"; 1774 $message .= __('Reason:', 'easy-invoice'); 1775 $message .= "\n"; 1776 $message .= $reason; 1777 } 1778 1779 $message .= "\n\n"; 1780 $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice'); 1781 $message .= "\n\n"; 1782 $message .= __('Regards,', 'easy-invoice'); 1783 $message .= "\n"; 1784 $message .= $company_name; 1785 1786 return $message; 1787 } 1788 1789 /** 1790 * Send admin notification for quote acceptance/decline 1791 * 1792 * @param Quote $quote The quote 1793 * @param string $action Action type ('accepted' or 'declined') 1794 * @return array Result array with success status and message 1795 */ 1796 public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array { 1797 try { 1798 // Validate quote 1799 if (!$quote || !$quote->getId()) { 1800 return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')]; 1801 } 1802 1803 // Get admin email 1804 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email'); 1805 if (empty($admin_email)) { 1806 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')]; 1807 } 1808 1809 // Prepare email subject 1810 $subject = sprintf( 1811 __('Quote %s has been %s', 'easy-invoice'), 1812 $quote->getNumber(), 1813 $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice') 1814 ); 1815 1816 // Prepare email message 1817 $message = $this->prepareAdminQuoteNotificationMessage($quote, $action); 1818 1819 // Add HTML wrapper if enabled 1820 if ($this->settings['enable_html'] === 'yes') { 1821 $message = $this->wrapInHtmlTemplate($message); 1822 } 1823 1824 // Prepare headers 1825 $headers = $this->prepareEmailHeaders(); 1826 1827 // Send email 1828 $sent = $this->sendEmail($admin_email, $subject, $message, $headers); 1829 1830 if ($sent) { 1831 do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action); 1832 return [ 1833 'success' => true, 1834 'message' => __('Admin notification sent successfully', 'easy-invoice') 1835 ]; 1836 } else { 1837 do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action); 1838 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')]; 1839 } 1840 1841 } catch (\Exception $e) { 1842 $this->log('Admin quote notification error: ' . $e->getMessage(), 'error'); 1843 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()]; 1844 } 1845 } 1846 1847 /** 1848 * Prepare admin quote notification message 1849 * 1850 * @param Quote $quote The quote 1851 * @param string $action Action type ('accepted' or 'declined') 1852 * @return string Email message 1853 */ 1854 private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string { 1855 $site_name = get_bloginfo('name'); 1856 $quote_number = $quote->getNumber(); 1857 $customer_name = $quote->getCustomerName(); 1858 1859 // Format amount 1860 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote); 1861 $formatted_amount = $formatter->format($quote->getTotal()); 1862 1863 $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice'); 1864 $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice'); 1865 1866 $message = __('Hello,', 'easy-invoice'); 1867 $message .= "\n\n"; 1868 $message .= sprintf( 1869 __('The quote %s for %s has been %s by the client.', 'easy-invoice'), 1870 $quote_number, 1871 $customer_name, 1872 $action_label 1873 ); 1874 $message .= "\n\n"; 1875 $message .= __('Quote Details:', 'easy-invoice'); 1876 $message .= "\n"; 1877 $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number); 1878 $message .= "\n"; 1879 $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name); 1880 $message .= "\n"; 1881 $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount); 1882 $message .= "\n"; 1883 $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format'))); 1884 $message .= "\n\n"; 1885 $message .= __('You can view the quote at:', 'easy-invoice'); 1886 $message .= "\n"; 1887 $message .= get_permalink($quote->getId()); 1888 $message .= "\n\n"; 1889 $message .= __('Best regards,', 'easy-invoice'); 1890 $message .= "\n"; 1891 $message .= $site_name; 1892 1893 return $message; 1894 } 1895 1896 /** 1897 * Handle payment completed hook 1898 * Sends admin notification and customer confirmation when payment is completed 1899 * 1900 * @param int $invoice_id Invoice ID 1901 * @param \EasyInvoice\Models\Invoice $invoice Invoice object 1902 * @param array $payment_data Payment data (method, gateway, transaction_id, amount) 1903 * @return void 1904 */ 1905 public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void { 1906 if (!$invoice || !$invoice->getId()) { 1907 return; 1908 } 1909 1910 // Send admin notification 1911 $this->sendAdminPaymentNotification($invoice, $payment_data); 1912 1913 // Send customer confirmation email (without BCC to admin since we already sent admin notification) 1914 // We pass a flag to skip BCC for payment confirmations 1915 $this->sendPaymentConfirmationEmail($invoice, array_merge($payment_data, ['skip_bcc' => true])); 1916 } 1917 1918 /** 1919 * Get payment method label 1920 * 1921 * @param string $method Payment method 1922 * @return string Payment method label 1923 */ 1924 private function getPaymentMethodLabel(string $method): string { 1925 $labels = [ 1926 'bank' => __('Bank Transfer', 'easy-invoice'), 1927 'cheque' => __('Cheque', 'easy-invoice'), 1928 'paypal' => __('PayPal', 'easy-invoice'), 1929 'stripe' => __('Stripe', 'easy-invoice'), 1930 'square' => __('Square', 'easy-invoice'), 1931 'mollie' => __('Mollie', 'easy-invoice'), 1932 'authorizenet' => __('Authorize.Net', 'easy-invoice'), 1933 'manual' => __('Manual Payment', 'easy-invoice'), 1934 'online' => __('Online Payment', 'easy-invoice'), 1935 ]; 1936 1937 return $labels[$method] ?? ucfirst($method); 1938 } 1939 1469 1940 } -
easy-invoice/tags/2.1.4/includes/Traits/PaymentCalculationTrait.php
r3346980 r3393362 86 86 } 87 87 88 // Trigger hook when invoice is marked as paid (for email notifications, etc.) 89 // Get payment method and transaction ID from the most recent payment 90 $payment_method = get_post_meta($invoice_id, '_payment_method', true) ?: $gateway_name ?: 'online'; 91 $transaction_id = get_post_meta($invoice_id, '_transaction_id', true) ?: ''; 92 $payment_amount = $invoice->getTotal(); 93 94 do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [ 95 'payment_method' => $payment_method, 96 'gateway_name' => $gateway_name, 97 'transaction_id' => $transaction_id, 98 'amount' => $payment_amount 99 ]); 100 88 101 return true; 89 102 } else { -
easy-invoice/tags/2.1.4/readme.txt
r3388559 r3393362 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 2.1. 37 Stable tag: 2.1.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Create professional invoices and quotes with PDF export and online payments. Complete WordPress invoice plugin for billing and client management.11 Create professional invoices, accept payments, and manage clients directly from WordPress. 12 12 13 13 == Description == … … 138 138 == Changelog == 139 139 140 = 2.1.4 - 2025-11-11 = 141 * Fixed - Email issue fixed 142 * Fixed - allowed html tag for textarea friend 143 * Fixed - Empty data Saving issue fixed for the description 140 144 141 145 = 2.1.3 - 2025-11-03 = -
easy-invoice/trunk/easy-invoice.php
r3388559 r3393362 4 4 * Plugin URI: https://matrixaddons.com/plugins/easy-invoice 5 5 * Description: A beautiful, full-featured invoicing solution for WordPress. Create professional invoices, quotes, and manage payments with ease. 6 * Version: 2.1. 36 * Version: 2.1.4 7 7 * Author: MatrixAddons 8 8 * Author URI: https://matrixaddons.com … … 25 25 26 26 // Define plugin constants. 27 define( 'EASY_INVOICE_VERSION', '2.1. 3' );27 define( 'EASY_INVOICE_VERSION', '2.1.4' ); 28 28 define( 'EASY_INVOICE_PLUGIN_FILE', __FILE__ ); 29 29 define( 'EASY_INVOICE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -
easy-invoice/trunk/includes/Admin/AdminController.php
r3345595 r3393362 38 38 */ 39 39 public function sendManualPaymentNotification($invoice_id, $payment_method) { 40 // Get admin email41 $admin_email = get_option('admin_email');42 43 40 // Get invoice details 44 41 $invoice = new \EasyInvoice\Models\Invoice(get_post($invoice_id)); 45 42 46 // Get customer details47 $customer_name = $invoice->getCustomerName();48 $customer_email = $invoice->getCustomerEmail();43 if (!$invoice || !$invoice->getId()) { 44 return; 45 } 49 46 50 // Format amount 51 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice); 52 $amount = $formatter->format($invoice->getTotal()); 53 54 // Format payment method 55 $payment_method_label = $payment_method === 'bank' ? 'Bank Transfer' : 'Cheque'; 56 57 // Email subject 58 $subject = sprintf( 59 __('New %s Payment Received - Invoice #%s', 'easy-invoice'), 60 $payment_method_label, 61 $invoice->getNumber() 62 ); 63 64 // Email message 65 $message = sprintf( 66 __('A new %s payment has been submitted for invoice #%s. 67 68 Invoice Details: 69 - Amount: %s 70 - Customer: %s 71 - Email: %s 72 73 Please review and verify this payment in the admin dashboard: 74 %s 75 76 This is an automated message from Easy Invoice.', 'easy-invoice'), 77 $payment_method_label, 78 $invoice->getNumber(), 79 $amount, 80 $customer_name, 81 $customer_email, 82 admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id) 83 ); 84 85 // Send email 86 wp_mail($admin_email, $subject, $message); 47 // Use EmailManager to send admin notification 48 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 49 $email_manager->sendAdminPaymentNotification($invoice, [ 50 'payment_method' => $payment_method 51 ]); 87 52 } 88 53 -
easy-invoice/trunk/includes/Controllers/PaymentController.php
r3346980 r3393362 694 694 $payment = Payment::create($payment_data); 695 695 696 // Store payment details before updating status (for the hook) 697 $invoice->setMeta('_payment_method', $payment_method); 698 if ($transaction_id) { 699 $invoice->setMeta('_transaction_id', $transaction_id); 700 } 701 696 702 // Update invoice status to paid only if total payments are sufficient 703 // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification 697 704 $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'manual'); 698 705 … … 765 772 private function sendPaymentConfirmationEmail($invoice_id, $payment_id): void { 766 773 $invoice = new Invoice(get_post($invoice_id)); 767 $customer_email = $invoice->getCustomerEmail(); 768 769 if (!$customer_email) { 770 return; 771 } 772 773 // Get currency settings 774 $settings_controller = new \EasyInvoice\Controllers\SettingsController(); 775 $settings = $settings_controller->getSettings(); 776 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; 777 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); 778 779 $site_name = get_bloginfo('name'); 780 $invoice_number = $invoice->getNumber(); 781 $amount = $invoice->getTotal(); 782 $formatted_amount = $currency_symbol . number_format($amount, 2); 783 784 $subject = sprintf(__('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number); 785 786 $message = sprintf( 787 __('Dear %s,', 'easy-invoice'), 788 $invoice->getCustomerName() 789 ); 790 $message .= "\n\n"; 791 $message .= sprintf( 792 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'), 793 $formatted_amount, 794 $invoice_number 795 ); 796 $message .= "\n\n"; 797 $message .= __('Thank you for your business.', 'easy-invoice'); 798 $message .= "\n\n"; 799 $message .= sprintf(__('Regards,', 'easy-invoice')); 800 $message .= "\n"; 801 $message .= get_option('easy_invoice_company_name', $site_name); 802 803 wp_mail($customer_email, $subject, $message); 774 775 if (!$invoice || !$invoice->getId()) { 776 return; 777 } 778 779 // Use EmailManager to send payment confirmation 780 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 781 $email_manager->sendPaymentConfirmationEmail($invoice, [ 782 'payment_id' => $payment_id 783 ]); 804 784 } 805 785 … … 812 792 private function sendPaymentRejectionEmail($invoice_id, $reason): void { 813 793 $invoice = new Invoice(get_post($invoice_id)); 814 $customer_email = $invoice->getCustomerEmail(); 815 816 if (!$customer_email) { 817 return; 818 } 819 820 // Get currency settings 821 $settings_controller = new \EasyInvoice\Controllers\SettingsController(); 822 $settings = $settings_controller->getSettings(); 823 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; 824 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); 825 826 $site_name = get_bloginfo('name'); 827 $invoice_number = $invoice->getNumber(); 828 $amount = $invoice->getTotal(); 829 $formatted_amount = $currency_symbol . number_format($amount, 2); 830 831 $subject = sprintf(__('[%s] Payment Issue - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number); 832 833 $message = sprintf( 834 __('Dear %s,', 'easy-invoice'), 835 $invoice->getCustomerName() 836 ); 837 $message .= "\n\n"; 838 $message .= sprintf( 839 __('We regret to inform you that we could not process your payment of %s for Invoice #%s.', 'easy-invoice'), 840 $formatted_amount, 841 $invoice_number 842 ); 843 $message .= "\n\n"; 844 845 if ($reason) { 846 $message .= __('Reason:', 'easy-invoice') . "\n"; 847 $message .= $reason; 848 $message .= "\n\n"; 849 } 850 851 $message .= __('Please contact us to arrange an alternative payment method.', 'easy-invoice'); 852 $message .= "\n\n"; 853 $message .= sprintf(__('Regards,', 'easy-invoice')); 854 $message .= "\n"; 855 $message .= get_option('easy_invoice_company_name', $site_name); 856 857 wp_mail($customer_email, $subject, $message); 794 795 if (!$invoice || !$invoice->getId()) { 796 return; 797 } 798 799 // Use EmailManager to send payment rejection 800 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 801 $email_manager->sendPaymentRejectionEmail($invoice, $reason); 858 802 } 859 803 … … 938 882 foreach ($pending_invoices as $post) { 939 883 $invoice = new Invoice($post); 940 $customer_email = $invoice->getCustomerEmail(); 941 942 if (!$customer_email) { 884 885 if (!$invoice || !$invoice->getId()) { 943 886 continue; 944 887 } 945 888 946 $site_name = get_bloginfo('name'); 947 $invoice_number = $invoice->getNumber(); 948 $amount = $invoice->getTotal(); 949 $formatted_amount = $currency_symbol . number_format($amount, 2); 950 $payment_method = get_post_meta($invoice->getId(), '_payment_method', true); 951 $payment_method_label = $payment_method === 'bank' ? __('Bank Transfer', 'easy-invoice') : __('Cheque', 'easy-invoice'); 952 953 $subject = sprintf(__('[%s] Payment Reminder - Invoice #%s', 'easy-invoice'), $site_name, $invoice_number); 954 955 $message = sprintf( 956 __('Dear %s,', 'easy-invoice'), 957 $invoice->getCustomerName() 958 ); 959 $message .= "\n\n"; 960 $message .= sprintf( 961 __('This is a friendly reminder that we are still awaiting your %s payment of %s for Invoice #%s.', 'easy-invoice'), 962 $payment_method_label, 963 $formatted_amount, 964 $invoice_number 965 ); 966 $message .= "\n\n"; 967 $message .= __('If you have already sent the payment, please disregard this reminder. If not, please arrange for payment at your earliest convenience.', 'easy-invoice'); 968 $message .= "\n\n"; 969 $message .= sprintf(__('Regards,', 'easy-invoice')); 970 $message .= "\n"; 971 $message .= get_option('easy_invoice_company_name', $site_name); 972 973 wp_mail($customer_email, $subject, $message); 974 975 // Mark reminder as sent 976 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql')); 889 // Use EmailManager to send payment reminder 890 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 891 $result = $email_manager->sendInvoiceEmail($invoice, 'reminder', [ 892 'payment_method' => get_post_meta($invoice->getId(), '_payment_method', true) 893 ]); 894 895 // Mark reminder as sent if email was sent successfully 896 if ($result['success']) { 897 update_post_meta($invoice->getId(), '_payment_reminder_sent', current_time('mysql')); 898 } 977 899 } 978 900 … … 1188 1110 } 1189 1111 1112 // Store payment details before updating status (for the hook) 1113 $transaction_id = get_post_meta($invoice_id, '_' . $payment_method . '_transaction_id', true) ?: 'MANUAL-' . $invoice_id; 1114 $invoice->setMeta('_payment_method', $payment_method); 1115 $invoice->setMeta('_transaction_id', $transaction_id); 1116 1117 // Update invoice status to paid 1118 // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification 1119 $invoice->setStatus('paid'); 1120 $invoice->save(); 1121 1122 // Trigger the payment completed hook manually since we're updating status directly 1123 do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [ 1124 'payment_method' => $payment_method, 1125 'gateway_name' => 'manual', 1126 'transaction_id' => $transaction_id, 1127 'amount' => $invoice->getTotal() 1128 ]); 1129 1190 1130 // Trigger email confirmation and actions only if we have a payment_id 1191 1131 if ($payment_id) { 1132 // Send confirmation email to customer 1192 1133 $this->sendPaymentConfirmationEmail($invoice_id, $payment_id); 1193 1134 do_action('easy_invoice_manual_payment_confirmed', $invoice_id, $payment_id, $payment_method); -
easy-invoice/trunk/includes/Controllers/QuoteController.php
r3387040 r3393362 1349 1349 */ 1350 1350 private function sendQuoteAcceptanceNotification($quote): void { 1351 $admin_email = get_option('admin_email'); 1352 $site_name = get_bloginfo('name'); 1353 1354 $subject = sprintf(__('Quote %s has been accepted', 'easy-invoice'), $quote->getNumber()); 1355 1356 $message = sprintf( 1357 __('Hello, 1358 1359 The quote %s for %s has been accepted by the client. 1360 1361 Quote Details: 1362 - Quote Number: %s 1363 - Client: %s 1364 - Total Amount: %s 1365 - Accepted Date: %s 1366 1367 You can view the quote at: %s 1368 1369 Best regards, 1370 %s', 'easy-invoice'), 1371 $quote->getNumber(), 1372 $quote->getCustomerName(), 1373 $quote->getNumber(), 1374 $quote->getCustomerName(), 1375 $this->formatCurrency($quote->getTotal(), $quote), 1376 date_i18n(get_option('date_format') . ' ' . get_option('time_format')), 1377 get_permalink($quote->getId()), 1378 $site_name 1379 ); 1380 1381 wp_mail($admin_email, $subject, $message); 1351 // Use EmailManager to send admin notification 1352 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 1353 $email_manager->sendAdminQuoteNotification($quote, 'accepted'); 1382 1354 } 1383 1355 … … 1388 1360 */ 1389 1361 private function sendQuoteDeclineNotification($quote): void { 1390 $admin_email = get_option('admin_email'); 1391 $site_name = get_bloginfo('name'); 1392 1393 $subject = sprintf(__('Quote %s has been declined', 'easy-invoice'), $quote->getNumber()); 1394 1395 $message = sprintf( 1396 __('Hello, 1397 1398 The quote %s for %s has been declined by the client. 1399 1400 Quote Details: 1401 - Quote Number: %s 1402 - Client: %s 1403 - Total Amount: %s 1404 - Declined Date: %s 1405 1406 You can view the quote at: %s 1407 1408 Best regards, 1409 %s', 'easy-invoice'), 1410 $quote->getNumber(), 1411 $quote->getCustomerName(), 1412 $quote->getNumber(), 1413 $quote->getCustomerName(), 1414 $this->formatCurrency($quote->getTotal(), $quote), 1415 date_i18n(get_option('date_format') . ' ' . get_option('time_format')), 1416 get_permalink($quote->getId()), 1417 $site_name 1418 ); 1419 1420 wp_mail($admin_email, $subject, $message); 1362 // Use EmailManager to send admin notification 1363 $email_manager = \EasyInvoice\Services\EmailManager::getInstance(); 1364 $email_manager->sendAdminQuoteNotification($quote, 'declined'); 1421 1365 } 1422 1366 -
easy-invoice/trunk/includes/Forms/FormProcessor.php
r3348168 r3393362 23 23 24 24 /** 25 * Allowed HTML tags for textarea fields 26 * 27 * @since 1.0.0 28 * @var array 29 */ 30 private static $allowed_textarea_tags = [ 31 'p' => [], 32 'br' => [], 33 'strong' => [], 34 'em' => [], 35 'i' => [], 36 'b' => [], 37 'u' => [], 38 'ul' => [], 39 'ol' => [], 40 'li' => [], 41 'h1' => [], 42 'h2' => [], 43 'h3' => [], 44 'h4' => [], 45 'h5' => [], 46 'h6' => [], 47 'a' => [ 48 'href' => [], 49 'title' => [], 50 'target' => [], 51 ], 52 'span' => [], 53 'div' => [], 54 ]; 55 56 /** 57 * Sanitize textarea field allowing basic HTML tags 58 * 59 * @since 1.0.0 60 * @param string $value The value to sanitize 61 * @return string Sanitized value with allowed HTML tags 62 */ 63 public static function sanitizeTextareaWithHtml(string $value): string { 64 // Use wp_kses to allow only safe HTML tags 65 return wp_kses($value, self::$allowed_textarea_tags); 66 } 67 68 /** 25 69 * Process form data 26 70 * … … 52 96 } 53 97 $required = $field['required'] ?? false; 98 $field_type = $field['type'] ?? 'text'; 54 99 $raw_value = $raw_data[$field_name] ?? ''; 55 100 … … 63 108 } 64 109 65 // Skip empty non-required fields 66 if (empty($raw_value) && !$required && $raw_value !== '0') { 110 // Always include textarea fields (like description) even when empty 111 // This allows users to clear these fields by submitting empty values 112 $always_include_fields = ['description', 'notes', 'terms', 'internal_notes']; 113 $should_always_include = in_array($field_name, $always_include_fields) || $field_type === 'textarea'; 114 115 // Skip empty non-required fields (unless they should always be included) 116 if (empty($raw_value) && !$required && $raw_value !== '0' && !$should_always_include) { 67 117 continue; 68 118 } … … 208 258 $value = $form_data[$field_name] ?? null; 209 259 210 211 212 // Only save if value exists and is not empty (or is 0) 213 if ($value !== null && $value !== '') { 260 // Fields that should always be saved, even if empty (to allow clearing) 261 $always_save_fields = ['description', 'notes', 'terms', 'internal_notes']; 262 $should_always_save = in_array($field_name, $always_save_fields); 263 264 // Save if value exists and is not empty (or is 0), OR if it's a field that should always be saved 265 if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) { 214 266 // Use custom save callback if provided 215 267 if (isset($field['save_callback']) && is_callable($field['save_callback'])) { 216 $field['save_callback']($value , $model);268 $field['save_callback']($value ?? '', $model); 217 269 } else { 218 270 // Default save to meta data 219 271 if (method_exists($model, 'setMetaData')) { 220 $model->setMetaData($db_key, $value); 221 222 272 $model->setMetaData($db_key, $value ?? ''); 223 273 } 224 274 } … … 227 277 228 278 // Also process any extra fields that might not be in the configuration 279 $always_save_fields = ['description', 'notes', 'terms', 'internal_notes']; 229 280 foreach ($form_data as $field_name => $value) { 230 if ($value !== null && $value !== '') { 281 $should_always_save = in_array($field_name, $always_save_fields); 282 283 if (($value !== null && $value !== '') || ($should_always_save && array_key_exists($field_name, $form_data))) { 231 284 $db_key = '_easy_invoice_' . $field_name; 232 285 … … 241 294 242 295 if (!$already_processed && method_exists($model, 'setMetaData')) { 243 $model->setMetaData($db_key, $value );296 $model->setMetaData($db_key, $value ?? ''); 244 297 } 245 298 } … … 293 346 294 347 case 'textarea': 295 return sanitize_textarea_field($value); 348 // Allow basic HTML tags in textarea fields 349 return self::sanitizeTextareaWithHtml($value); 296 350 297 351 case 'email': … … 324 378 325 379 if (is_callable($sanitize_callback)) { 380 // If callback is 'sanitize_textarea_field', use our HTML-allowing version for textarea fields 381 if ($sanitize_callback === 'sanitize_textarea_field' && ($field['type'] ?? '') === 'textarea') { 382 return self::sanitizeTextareaWithHtml($value); 383 } 326 384 return $sanitize_callback($value); 327 385 } -
easy-invoice/trunk/includes/Gateways/PayPalGateway.php
r3346980 r3393362 539 539 540 540 // Update invoice status to paid only if total payments are sufficient 541 // This will trigger 'easy_invoice_payment_completed' hook which sends admin notification 541 542 $this->updateInvoiceStatusIfPaid($invoice_id, $invoice, 'paypal'); 542 543 … … 546 547 wp_update_post(['ID' => $invoice_id, 'post_status' => 'publish']); 547 548 } 549 550 // Store payment details for the hook 551 $invoice->setMeta('_payment_method', 'paypal'); 552 $invoice->setMeta('_transaction_id', $paypal_txn_id); 548 553 549 554 return [ -
easy-invoice/trunk/includes/Models/Invoice.php
r3348168 r3393362 446 446 447 447 // Get the value from dynamic data array 448 $value = $this->data[$field_name] ?? null; 449 if ($value !== null) { 448 // Save all fields that exist in the data array (including empty strings to allow clearing fields) 449 // If a field exists in $this->data, it means it was explicitly set, so we should save it 450 if (array_key_exists($field_name, $this->data)) { 451 $value = $this->data[$field_name]; 450 452 update_post_meta($this->id, $meta_key, $value); 451 453 } -
easy-invoice/trunk/includes/Models/Quote.php
r3363158 r3393362 486 486 487 487 // Get the value from dynamic data array 488 $value = $this->data[$field_name] ?? null;489 if ($value !== null) {490 491 488 // Save all fields that exist in the data array (including empty strings to allow clearing fields) 489 // If a field exists in $this->data, it means it was explicitly set, so we should save it 490 if (array_key_exists($field_name, $this->data)) { 491 $value = $this->data[$field_name]; 492 492 update_post_meta($this->id, $meta_key, $value); 493 494 // Debug: Log template field saving to database495 496 493 } 497 494 } -
easy-invoice/trunk/includes/Repositories/InvoiceRepository.php
r3387051 r3393362 397 397 } 398 398 399 if (isset($data['description'])) { 400 $invoice->setDescription($data['description']); 401 } 402 403 if (isset($data['invoice_description'])) { 404 $invoice->setDescription($data['invoice_description']); 399 // Always update description if it exists in data (even if empty, to allow clearing) 400 if (array_key_exists('description', $data)) { 401 $invoice->setDescription($data['description'] ?? ''); 402 } 403 404 if (array_key_exists('invoice_description', $data)) { 405 $invoice->setDescription($data['invoice_description'] ?? ''); 405 406 } 406 407 -
easy-invoice/trunk/includes/Repositories/QuoteRepository.php
r3387051 r3393362 495 495 } 496 496 497 if (isset($data['description'])) { 498 $quote->setDescription($data['description']); 497 // Always update description if it exists in data (even if empty, to allow clearing) 498 if (array_key_exists('description', $data)) { 499 $quote->setDescription($data['description'] ?? ''); 499 500 } 500 501 -
easy-invoice/trunk/includes/Services/EmailManager.php
r3387040 r3393362 109 109 add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3); 110 110 add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3); 111 112 // Listen for payment completion to send admin notifications 113 add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3); 111 114 } 112 115 … … 1467 1470 } 1468 1471 1472 /** 1473 * Send admin notification when payment is received 1474 * 1475 * @param Invoice $invoice The invoice 1476 * @param array $payment_data Payment data (method, amount, etc.) 1477 * @return array Result array with success status and message 1478 */ 1479 public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array { 1480 try { 1481 // Validate invoice 1482 if (!$invoice || !$invoice->getId()) { 1483 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; 1484 } 1485 1486 // Get admin email 1487 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email'); 1488 if (empty($admin_email)) { 1489 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')]; 1490 } 1491 1492 // Get payment method 1493 $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online'; 1494 $payment_method_label = $this->getPaymentMethodLabel($payment_method); 1495 1496 // Format amount 1497 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice); 1498 $amount = $formatter->format($invoice->getTotal()); 1499 1500 // Prepare email subject 1501 $subject = sprintf( 1502 __('New Payment Received - Invoice #%s', 'easy-invoice'), 1503 $invoice->getNumber() 1504 ); 1505 1506 // Prepare email message 1507 $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data); 1508 1509 // Add HTML wrapper if enabled 1510 if ($this->settings['enable_html'] === 'yes') { 1511 $message = $this->wrapInHtmlTemplate($message); 1512 } 1513 1514 // Prepare headers 1515 $headers = $this->prepareEmailHeaders(); 1516 1517 // Send email 1518 $sent = $this->sendEmail($admin_email, $subject, $message, $headers); 1519 1520 if ($sent) { 1521 do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data); 1522 return [ 1523 'success' => true, 1524 'message' => __('Admin notification sent successfully', 'easy-invoice') 1525 ]; 1526 } else { 1527 do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data); 1528 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')]; 1529 } 1530 1531 } catch (\Exception $e) { 1532 $this->log('Admin payment notification error: ' . $e->getMessage(), 'error'); 1533 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()]; 1534 } 1535 } 1536 1537 /** 1538 * Send payment confirmation email to customer 1539 * 1540 * @param Invoice $invoice The invoice 1541 * @param array $payment_data Payment data 1542 * @return array Result array with success status and message 1543 */ 1544 public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array { 1545 try { 1546 // Validate invoice 1547 if (!$invoice || !$invoice->getId()) { 1548 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; 1549 } 1550 1551 // Get customer email 1552 $customer_email = $invoice->getCustomerEmail(); 1553 if (empty($customer_email)) { 1554 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')]; 1555 } 1556 1557 // Get currency settings 1558 $settings_controller = new \EasyInvoice\Controllers\SettingsController(); 1559 $settings = $settings_controller->getSettings(); 1560 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD'; 1561 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code); 1562 1563 // Format amount 1564 $amount = $invoice->getTotal(); 1565 $formatted_amount = $currency_symbol . number_format($amount, 2); 1566 1567 // Prepare email subject 1568 $site_name = get_bloginfo('name'); 1569 $subject = sprintf( 1570 __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'), 1571 $site_name, 1572 $invoice->getNumber() 1573 ); 1574 1575 // Prepare email message 1576 $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount); 1577 1578 // Add HTML wrapper if enabled 1579 if ($this->settings['enable_html'] === 'yes') { 1580 $message = $this->wrapInHtmlTemplate($message); 1581 } 1582 1583 // Prepare headers 1584 $headers = $this->prepareEmailHeaders(); 1585 1586 // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate) 1587 // The payment completion hook already sends a dedicated admin notification 1588 $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true; 1589 if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { 1590 $headers[] = 'Bcc: ' . $this->settings['admin_email']; 1591 } 1592 1593 // Send email 1594 $sent = $this->sendEmail($customer_email, $subject, $message, $headers); 1595 1596 if ($sent) { 1597 do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data); 1598 return [ 1599 'success' => true, 1600 'message' => __('Payment confirmation email sent successfully', 'easy-invoice') 1601 ]; 1602 } else { 1603 do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data); 1604 return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')]; 1605 } 1606 1607 } catch (\Exception $e) { 1608 $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error'); 1609 return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()]; 1610 } 1611 } 1612 1613 /** 1614 * Send payment rejection email to customer 1615 * 1616 * @param Invoice $invoice The invoice 1617 * @param string $reason Rejection reason 1618 * @return array Result array with success status and message 1619 */ 1620 public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array { 1621 try { 1622 // Validate invoice 1623 if (!$invoice || !$invoice->getId()) { 1624 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')]; 1625 } 1626 1627 // Get customer email 1628 $customer_email = $invoice->getCustomerEmail(); 1629 if (empty($customer_email)) { 1630 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')]; 1631 } 1632 1633 // Prepare email subject 1634 $site_name = get_bloginfo('name'); 1635 $subject = sprintf( 1636 __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'), 1637 $site_name, 1638 $invoice->getNumber() 1639 ); 1640 1641 // Prepare email message 1642 $message = $this->preparePaymentRejectionMessage($invoice, $reason); 1643 1644 // Add HTML wrapper if enabled 1645 if ($this->settings['enable_html'] === 'yes') { 1646 $message = $this->wrapInHtmlTemplate($message); 1647 } 1648 1649 // Prepare headers 1650 $headers = $this->prepareEmailHeaders(); 1651 1652 // Add BCC to admin if enabled 1653 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) { 1654 $headers[] = 'Bcc: ' . $this->settings['admin_email']; 1655 } 1656 1657 // Send email 1658 $sent = $this->sendEmail($customer_email, $subject, $message, $headers); 1659 1660 if ($sent) { 1661 do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason); 1662 return [ 1663 'success' => true, 1664 'message' => __('Payment rejection email sent successfully', 'easy-invoice') 1665 ]; 1666 } else { 1667 do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason); 1668 return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')]; 1669 } 1670 1671 } catch (\Exception $e) { 1672 $this->log('Payment rejection email error: ' . $e->getMessage(), 'error'); 1673 return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()]; 1674 } 1675 } 1676 1677 /** 1678 * Prepare admin payment notification message 1679 * 1680 * @param Invoice $invoice The invoice 1681 * @param string $payment_method_label Payment method label 1682 * @param string $amount Formatted amount 1683 * @param array $payment_data Payment data 1684 * @return string Email message 1685 */ 1686 private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string { 1687 $invoice_number = $invoice->getNumber(); 1688 $customer_name = $invoice->getCustomerName(); 1689 $customer_email = $invoice->getCustomerEmail(); 1690 $invoice_id = $invoice->getId(); 1691 1692 $message = sprintf( 1693 __('A new %s payment has been received for invoice #%s.', 'easy-invoice'), 1694 $payment_method_label, 1695 $invoice_number 1696 ); 1697 $message .= "\n\n"; 1698 $message .= __('Invoice Details:', 'easy-invoice'); 1699 $message .= "\n"; 1700 $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount); 1701 $message .= "\n"; 1702 $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name); 1703 $message .= "\n"; 1704 $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email); 1705 1706 // Add transaction ID if available 1707 if (!empty($payment_data['transaction_id'])) { 1708 $message .= "\n"; 1709 $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']); 1710 } 1711 1712 $message .= "\n\n"; 1713 $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice'); 1714 $message .= "\n"; 1715 $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id); 1716 $message .= "\n\n"; 1717 $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice'); 1718 1719 return $message; 1720 } 1721 1722 /** 1723 * Prepare payment confirmation message 1724 * 1725 * @param Invoice $invoice The invoice 1726 * @param string $formatted_amount Formatted amount 1727 * @return string Email message 1728 */ 1729 private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string { 1730 $customer_name = $invoice->getCustomerName(); 1731 $invoice_number = $invoice->getNumber(); 1732 $site_name = get_bloginfo('name'); 1733 $company_name = get_option('easy_invoice_company_name', $site_name); 1734 1735 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name); 1736 $message .= "\n\n"; 1737 $message .= sprintf( 1738 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'), 1739 $formatted_amount, 1740 $invoice_number 1741 ); 1742 $message .= "\n\n"; 1743 $message .= __('Thank you for your business.', 'easy-invoice'); 1744 $message .= "\n\n"; 1745 $message .= __('Regards,', 'easy-invoice'); 1746 $message .= "\n"; 1747 $message .= $company_name; 1748 1749 return $message; 1750 } 1751 1752 /** 1753 * Prepare payment rejection message 1754 * 1755 * @param Invoice $invoice The invoice 1756 * @param string $reason Rejection reason 1757 * @return string Email message 1758 */ 1759 private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string { 1760 $customer_name = $invoice->getCustomerName(); 1761 $invoice_number = $invoice->getNumber(); 1762 $site_name = get_bloginfo('name'); 1763 $company_name = get_option('easy_invoice_company_name', $site_name); 1764 1765 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name); 1766 $message .= "\n\n"; 1767 $message .= sprintf( 1768 __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'), 1769 $invoice_number 1770 ); 1771 1772 if (!empty($reason)) { 1773 $message .= "\n\n"; 1774 $message .= __('Reason:', 'easy-invoice'); 1775 $message .= "\n"; 1776 $message .= $reason; 1777 } 1778 1779 $message .= "\n\n"; 1780 $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice'); 1781 $message .= "\n\n"; 1782 $message .= __('Regards,', 'easy-invoice'); 1783 $message .= "\n"; 1784 $message .= $company_name; 1785 1786 return $message; 1787 } 1788 1789 /** 1790 * Send admin notification for quote acceptance/decline 1791 * 1792 * @param Quote $quote The quote 1793 * @param string $action Action type ('accepted' or 'declined') 1794 * @return array Result array with success status and message 1795 */ 1796 public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array { 1797 try { 1798 // Validate quote 1799 if (!$quote || !$quote->getId()) { 1800 return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')]; 1801 } 1802 1803 // Get admin email 1804 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email'); 1805 if (empty($admin_email)) { 1806 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')]; 1807 } 1808 1809 // Prepare email subject 1810 $subject = sprintf( 1811 __('Quote %s has been %s', 'easy-invoice'), 1812 $quote->getNumber(), 1813 $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice') 1814 ); 1815 1816 // Prepare email message 1817 $message = $this->prepareAdminQuoteNotificationMessage($quote, $action); 1818 1819 // Add HTML wrapper if enabled 1820 if ($this->settings['enable_html'] === 'yes') { 1821 $message = $this->wrapInHtmlTemplate($message); 1822 } 1823 1824 // Prepare headers 1825 $headers = $this->prepareEmailHeaders(); 1826 1827 // Send email 1828 $sent = $this->sendEmail($admin_email, $subject, $message, $headers); 1829 1830 if ($sent) { 1831 do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action); 1832 return [ 1833 'success' => true, 1834 'message' => __('Admin notification sent successfully', 'easy-invoice') 1835 ]; 1836 } else { 1837 do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action); 1838 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')]; 1839 } 1840 1841 } catch (\Exception $e) { 1842 $this->log('Admin quote notification error: ' . $e->getMessage(), 'error'); 1843 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()]; 1844 } 1845 } 1846 1847 /** 1848 * Prepare admin quote notification message 1849 * 1850 * @param Quote $quote The quote 1851 * @param string $action Action type ('accepted' or 'declined') 1852 * @return string Email message 1853 */ 1854 private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string { 1855 $site_name = get_bloginfo('name'); 1856 $quote_number = $quote->getNumber(); 1857 $customer_name = $quote->getCustomerName(); 1858 1859 // Format amount 1860 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote); 1861 $formatted_amount = $formatter->format($quote->getTotal()); 1862 1863 $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice'); 1864 $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice'); 1865 1866 $message = __('Hello,', 'easy-invoice'); 1867 $message .= "\n\n"; 1868 $message .= sprintf( 1869 __('The quote %s for %s has been %s by the client.', 'easy-invoice'), 1870 $quote_number, 1871 $customer_name, 1872 $action_label 1873 ); 1874 $message .= "\n\n"; 1875 $message .= __('Quote Details:', 'easy-invoice'); 1876 $message .= "\n"; 1877 $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number); 1878 $message .= "\n"; 1879 $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name); 1880 $message .= "\n"; 1881 $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount); 1882 $message .= "\n"; 1883 $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format'))); 1884 $message .= "\n\n"; 1885 $message .= __('You can view the quote at:', 'easy-invoice'); 1886 $message .= "\n"; 1887 $message .= get_permalink($quote->getId()); 1888 $message .= "\n\n"; 1889 $message .= __('Best regards,', 'easy-invoice'); 1890 $message .= "\n"; 1891 $message .= $site_name; 1892 1893 return $message; 1894 } 1895 1896 /** 1897 * Handle payment completed hook 1898 * Sends admin notification and customer confirmation when payment is completed 1899 * 1900 * @param int $invoice_id Invoice ID 1901 * @param \EasyInvoice\Models\Invoice $invoice Invoice object 1902 * @param array $payment_data Payment data (method, gateway, transaction_id, amount) 1903 * @return void 1904 */ 1905 public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void { 1906 if (!$invoice || !$invoice->getId()) { 1907 return; 1908 } 1909 1910 // Send admin notification 1911 $this->sendAdminPaymentNotification($invoice, $payment_data); 1912 1913 // Send customer confirmation email (without BCC to admin since we already sent admin notification) 1914 // We pass a flag to skip BCC for payment confirmations 1915 $this->sendPaymentConfirmationEmail($invoice, array_merge($payment_data, ['skip_bcc' => true])); 1916 } 1917 1918 /** 1919 * Get payment method label 1920 * 1921 * @param string $method Payment method 1922 * @return string Payment method label 1923 */ 1924 private function getPaymentMethodLabel(string $method): string { 1925 $labels = [ 1926 'bank' => __('Bank Transfer', 'easy-invoice'), 1927 'cheque' => __('Cheque', 'easy-invoice'), 1928 'paypal' => __('PayPal', 'easy-invoice'), 1929 'stripe' => __('Stripe', 'easy-invoice'), 1930 'square' => __('Square', 'easy-invoice'), 1931 'mollie' => __('Mollie', 'easy-invoice'), 1932 'authorizenet' => __('Authorize.Net', 'easy-invoice'), 1933 'manual' => __('Manual Payment', 'easy-invoice'), 1934 'online' => __('Online Payment', 'easy-invoice'), 1935 ]; 1936 1937 return $labels[$method] ?? ucfirst($method); 1938 } 1939 1469 1940 } -
easy-invoice/trunk/includes/Traits/PaymentCalculationTrait.php
r3346980 r3393362 86 86 } 87 87 88 // Trigger hook when invoice is marked as paid (for email notifications, etc.) 89 // Get payment method and transaction ID from the most recent payment 90 $payment_method = get_post_meta($invoice_id, '_payment_method', true) ?: $gateway_name ?: 'online'; 91 $transaction_id = get_post_meta($invoice_id, '_transaction_id', true) ?: ''; 92 $payment_amount = $invoice->getTotal(); 93 94 do_action('easy_invoice_payment_completed', $invoice_id, $invoice, [ 95 'payment_method' => $payment_method, 96 'gateway_name' => $gateway_name, 97 'transaction_id' => $transaction_id, 98 'amount' => $payment_amount 99 ]); 100 88 101 return true; 89 102 } else { -
easy-invoice/trunk/readme.txt
r3388559 r3393362 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 2.1. 37 Stable tag: 2.1.4 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html 10 10 11 Create professional invoices and quotes with PDF export and online payments. Complete WordPress invoice plugin for billing and client management.11 Create professional invoices, accept payments, and manage clients directly from WordPress. 12 12 13 13 == Description == … … 138 138 == Changelog == 139 139 140 = 2.1.4 - 2025-11-11 = 141 * Fixed - Email issue fixed 142 * Fixed - allowed html tag for textarea friend 143 * Fixed - Empty data Saving issue fixed for the description 140 144 141 145 = 2.1.3 - 2025-11-03 =
Note: See TracChangeset
for help on using the changeset viewer.