PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.3
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Services / EmailManager.php

EmailManager.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.3, at includes/Services/EmailManager.php

1,889 lines 75.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email Manager Service
4 *
5 * Handles all email functionality for Easy Invoice
6 *
7 * @package EasyInvoice
8 * @subpackage Services
9 * @since 1.0.0
10 */
11
12 namespace EasyInvoice\Services;
13
14 use EasyInvoice\Models\Invoice;
15 use EasyInvoice\Models\Quote;
16 use EasyInvoice\Models\Client;
17
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * EmailManager Class
24 *
25 * Centralized email management for Easy Invoice
26 */
27 class EmailManager extends BaseService {
28
29 /**
30 * Singleton instance
31 *
32 * @var EmailManager|null
33 */
34 private static $instance = null;
35
36 /**
37 * Service name
38 *
39 * @var string
40 */
41 protected $service_name = 'email_manager';
42
43 /**
44 * Email templates
45 *
46 * @var array
47 */
48 private $templates = [];
49
50 /**
51 * Email settings
52 *
53 * @var array
54 */
55 private $settings = [];
56
57 /**
58 * Get singleton instance
59 *
60 * @return EmailManager
61 */
62 public static function getInstance(): EmailManager {
63 if (self::$instance === null) {
64 self::$instance = new self();
65 }
66 return self::$instance;
67 }
68
69 /**
70 * Constructor
71 */
72 public function __construct() {
73 parent::__construct();
74 $this->loadSettings();
75 $this->loadTemplates();
76 $this->initHooks();
77 }
78
79 /**
80 * Initialize hooks
81 */
82 private function initHooks(): void {
83 // Invoice/quote send-email AJAX is handled exclusively by EasyInvoice\Admin\EasyInvoiceAjax
84 // (published-document checks, single handler) to avoid duplicate nopriv callbacks.
85
86 // Add email settings to admin
87 add_action('admin_init', [$this, 'registerEmailSettings']);
88 add_filter('easy_invoice_settings_sections', [$this, 'addEmailSettingsSection']);
89
90 // Refresh settings when they're updated
91 add_action('update_option_easy_invoice_email_from_name', [$this, 'refreshSettings']);
92 add_action('update_option_easy_invoice_email_from_address', [$this, 'refreshSettings']);
93 add_action('update_option_easy_invoice_email_reply_to', [$this, 'refreshSettings']);
94 add_action('update_option_easy_invoice_email_reply_to_name', [$this, 'refreshSettings']);
95 add_action('update_option_easy_invoice_enable_email_styling', [$this, 'refreshSettings']);
96 add_action('update_option_easy_invoice_email_logo', [$this, 'refreshSettings']);
97 add_action('update_option_easy_invoice_email_footer_text', [$this, 'refreshSettings']);
98 add_action('update_option_easy_invoice_bcc_admin', [$this, 'refreshSettings']);
99 add_action('update_option_easy_invoice_admin_email', [$this, 'refreshSettings']);
100 add_action('update_option_easy_invoice_email_subject', [$this, 'refreshSettings']);
101 add_action('update_option_easy_invoice_email_body', [$this, 'refreshSettings']);
102 add_action('update_option_easy_invoice_quote_subject', [$this, 'refreshSettings']);
103 add_action('update_option_easy_invoice_quote_body', [$this, 'refreshSettings']);
104
105 // Add email logs
106 add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3);
107 add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 10, 3);
108
109 // Listen for payment completion to send admin notifications
110 add_action('easy_invoice_payment_completed', [$this, 'handlePaymentCompleted'], 10, 3);
111 }
112
113 /**
114 * Load email settings
115 */
116 private function loadSettings(): void {
117 $this->settings = [
118 'from_name' => get_option('easy_invoice_email_from_name', get_bloginfo('name')),
119 'from_email' => get_option('easy_invoice_email_from_address', get_bloginfo('admin_email')),
120 'reply_to_email' => get_option('easy_invoice_email_reply_to', ''),
121 'reply_to_name' => get_option('easy_invoice_email_reply_to_name', ''),
122 'enable_html' => get_option('easy_invoice_enable_email_styling', 'yes'),
123 'email_logo' => get_option('easy_invoice_email_logo', ''),
124 'footer_text' => get_option('easy_invoice_email_footer_text', ''),
125 'bcc_admin' => get_option('easy_invoice_bcc_admin', 'no'),
126 'admin_email' => get_option('easy_invoice_admin_email', get_option('admin_email')),
127 ];
128 }
129
130 /**
131 * Load email templates
132 */
133 private function loadTemplates(): void {
134 $this->templates = [
135 'invoice_new' => [
136 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes',
137 'subject' => get_option('easy_invoice_invoice_email_subject', __('Your Invoice #{{invoice_number}} from {{company_name}}', 'easy-invoice')),
138 'body' => get_option('easy_invoice_invoice_email_body', $this->getDefaultInvoiceTemplate()),
139 'type' => 'invoice'
140 ],
141 'invoice_reminder' => [
142 'enabled' => get_option('easy_invoice_invoice_email_enabled', 'yes') === 'yes',
143 'subject' => get_option('easy_invoice_reminder_subject', 'Payment Reminder - Invoice #{{invoice_number}}'),
144 'body' => get_option('easy_invoice_reminder_body', $this->getDefaultReminderTemplate()),
145 'type' => 'invoice'
146 ],
147 'invoice_paid' => [
148 'enabled' => get_option('easy_invoice_payment_email_enabled', 'yes') === 'yes',
149 'subject' => get_option('easy_invoice_payment_email_subject', __('Payment Received - Invoice #{{invoice_number}}', 'easy-invoice')),
150 'body' => get_option('easy_invoice_payment_email_body', $this->getDefaultPaymentTemplate()),
151 'type' => 'invoice'
152 ],
153 'quote_new' => [
154 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes',
155 'subject' => get_option('easy_invoice_quote_email_subject', __('Your Quote #{{quote_number}} from {{company_name}}', 'easy-invoice')),
156 'body' => get_option('easy_invoice_quote_email_body', $this->getDefaultQuoteTemplate()),
157 'type' => 'quote'
158 ],
159 'quote_accepted' => [
160 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes',
161 'subject' => get_option('easy_invoice_quote_accepted_subject', 'Quote Accepted - #{{quote_number}}'),
162 'body' => get_option('easy_invoice_quote_accepted_body', $this->getDefaultQuoteAcceptedTemplate()),
163 'type' => 'quote'
164 ],
165 'quote_declined' => [
166 'enabled' => get_option('easy_invoice_quote_email_enabled', 'yes') === 'yes',
167 'subject' => get_option('easy_invoice_quote_declined_subject', 'Quote Declined - #{{quote_number}}'),
168 'body' => get_option('easy_invoice_quote_declined_body', $this->getDefaultQuoteDeclinedTemplate()),
169 'type' => 'quote'
170 ]
171 ];
172 }
173
174 /**
175 * Send invoice email
176 *
177 * @param Invoice $invoice The invoice
178 * @param string $template_type Template type (new, reminder, paid)
179 * @param array $additional_data Additional data for template
180 * @return array Result array with success status and message
181 */
182 public function sendInvoiceEmail(Invoice $invoice, string $template_type = 'new', array $additional_data = []): array {
183 try {
184 // Validate invoice
185 if (!$invoice || !$invoice->getId()) {
186 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
187 }
188
189 // Get client email
190 $client_email = $invoice->getCustomerEmail();
191 if (empty($client_email)) {
192 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
193 }
194
195 // Get template
196 $template_key = 'invoice_' . $template_type;
197 if (!isset($this->templates[$template_key])) {
198 return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')];
199 }
200
201 $template = $this->templates[$template_key];
202
203 // Check if email is enabled
204 if (!$template['enabled']) {
205 return ['success' => false, 'message' => __('Invoice available email is disabled', 'easy-invoice')];
206 }
207
208 // Prepare email data
209 $email_data = $this->prepareInvoiceEmailData($invoice, $template, $additional_data);
210
211 // Send email
212 $sent = $this->sendEmail(
213 $email_data['to'],
214 $email_data['subject'],
215 $email_data['message'],
216 $email_data['headers']
217 );
218
219 if ($sent) {
220 // Log success
221 do_action('easy_invoice_email_sent', $invoice, $client_email, $template_type);
222
223 return [
224 'success' => true,
225 'message' => __('Email sent successfully', 'easy-invoice'),
226 'email_data' => $email_data
227 ];
228 } else {
229 // Log failure
230 do_action('easy_invoice_email_failed', $invoice, $client_email, $template_type);
231
232 return ['success' => false, 'message' => __('Failed to send email', 'easy-invoice')];
233 }
234
235 } catch (\Exception $e) {
236 $this->log('Email sending error: ' . $e->getMessage(), 'error');
237 return ['success' => false, 'message' => __('Error sending email: ', 'easy-invoice') . $e->getMessage()];
238 }
239 }
240
241 /**
242 * Send quote email
243 *
244 * @param Quote $quote The quote
245 * @param string $template_type Template type (new, accepted, declined)
246 * @param array $additional_data Additional data for template
247 * @return array Result array with success status and message
248 */
249 public function sendQuoteEmail(Quote $quote, string $template_type = 'new', array $additional_data = []): array {
250 try {
251 // Validate quote
252 if (!$quote || !$quote->getId()) {
253 return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')];
254 }
255
256 // Get client email
257 $client_email = $quote->getCustomerEmail();
258 if (empty($client_email)) {
259 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
260 }
261
262 // Get template
263 $template_key = 'quote_' . $template_type;
264 if (!isset($this->templates[$template_key])) {
265 return ['success' => false, 'message' => __('Email template not found', 'easy-invoice')];
266 }
267
268 $template = $this->templates[$template_key];
269
270 // Check if email is enabled
271 if (!$template['enabled']) {
272 return ['success' => false, 'message' => __('Quote available email is disabled', 'easy-invoice')];
273 }
274
275 // Prepare email data
276 $email_data = $this->prepareQuoteEmailData($quote, $template, $additional_data);
277
278 // Send email
279 $sent = $this->sendEmail(
280 $email_data['to'],
281 $email_data['subject'],
282 $email_data['message'],
283 $email_data['headers']
284 );
285
286 if ($sent) {
287 // Log success
288 do_action('easy_invoice_quote_email_sent', $quote, $client_email, $template_type);
289
290 return [
291 'success' => true,
292 'message' => __('Quote email sent successfully', 'easy-invoice'),
293 'email_data' => $email_data
294 ];
295 } else {
296 // Log failure
297 do_action('easy_invoice_quote_email_failed', $quote, $client_email, $template_type);
298
299 return ['success' => false, 'message' => __('Failed to send quote email', 'easy-invoice')];
300 }
301
302 } catch (\Exception $e) {
303 $this->log('Quote email sending error: ' . $e->getMessage(), 'error');
304 return ['success' => false, 'message' => __('Error sending quote email: ', 'easy-invoice') . $e->getMessage()];
305 }
306 }
307
308 /**
309 * Prepare invoice email data
310 *
311 * @param Invoice $invoice The invoice
312 * @param array $template The email template
313 * @param array $additional_data Additional data
314 * @return array Email data
315 */
316 private function prepareInvoiceEmailData(Invoice $invoice, array $template, array $additional_data = []): array {
317 // Get replacements
318 $replacements = $this->getInvoiceReplacements($invoice, $additional_data);
319
320 // Process template
321 $subject = $this->processTemplate($template['subject'], $replacements);
322 $message = $this->processTemplate($template['body'], $replacements);
323 $message = do_shortcode($message); // Render shortcodes like [easy_invoice_url ...]
324
325 // Add HTML wrapper if enabled
326 if ($this->settings['enable_html'] === 'yes') {
327 $message = $this->wrapInHtmlTemplate($message);
328 }
329
330 // Prepare headers
331 $headers = $this->prepareEmailHeaders();
332
333 // Add BCC to admin if enabled
334 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
335 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
336 }
337
338 return [
339 'to' => $invoice->getCustomerEmail(),
340 'subject' => $subject,
341 'message' => $message,
342 'headers' => $headers
343 ];
344 }
345
346 /**
347 * Prepare quote email data
348 *
349 * @param Quote $quote The quote
350 * @param array $template The email template
351 * @param array $additional_data Additional data
352 * @return array Email data
353 */
354 private function prepareQuoteEmailData(Quote $quote, array $template, array $additional_data = []): array {
355 // Get replacements
356 $replacements = $this->getQuoteReplacements($quote, $additional_data);
357
358 // Process template
359 $subject = $this->processTemplate($template['subject'], $replacements);
360 $message = $this->processTemplate($template['body'], $replacements);
361
362 // Add HTML wrapper if enabled
363 if ($this->settings['enable_html'] === 'yes') {
364 $message = $this->wrapInHtmlTemplate($message);
365 }
366
367 // Prepare headers
368 $headers = $this->prepareEmailHeaders();
369
370 // Add BCC to admin if enabled
371 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
372 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
373 }
374
375 return [
376 'to' => $quote->getCustomerEmail(),
377 'subject' => $subject,
378 'message' => $message,
379 'headers' => $headers
380 ];
381 }
382
383 /**
384 * Prepare payment email data
385 *
386 * @param Invoice $invoice The invoice
387 * @param array $template The email template
388 * @param array $payment_data Payment data
389 * @return array Email data
390 */
391 private function preparePaymentEmailData(Invoice $invoice, array $template, array $payment_data = []): array {
392 // Get replacements
393 $replacements = $this->getPaymentReplacements($invoice, $payment_data);
394
395 // Process template
396 $subject = $this->processTemplate($template['subject'], $replacements);
397 $message = $this->processTemplate($template['body'], $replacements);
398
399 // Add HTML wrapper if enabled
400 if ($this->settings['enable_html'] === 'yes') {
401 $message = $this->wrapInHtmlTemplate($message);
402 }
403
404 // Prepare headers
405 $headers = $this->prepareEmailHeaders();
406
407 // Add BCC to admin if enabled
408 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
409 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
410 }
411
412 return [
413 'to' => $invoice->getCustomerEmail(),
414 'subject' => $subject,
415 'message' => $message,
416 'headers' => $headers
417 ];
418 }
419
420 /**
421 * Get invoice replacements
422 *
423 * @param Invoice $invoice The invoice
424 * @param array $additional_data Additional data
425 * @return array Replacements
426 */
427 private function getInvoiceReplacements(Invoice $invoice, array $additional_data = []): array {
428 $currency_symbol = get_option('easy_invoice_currency_symbol', '$');
429
430 // Secure link support
431 $invoice_url = get_permalink($invoice->getId());
432 $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
433 if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
434 $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId());
435 if ($secure_url) {
436 $invoice_url = $secure_url;
437 }
438 }
439
440 // Get client data for additional fields
441 $client = null;
442 if ($invoice->getClientId()) {
443 $client_repository = new \EasyInvoice\Repositories\ClientRepository();
444 $client = $client_repository->find($invoice->getClientId());
445 }
446
447 return array_merge([
448 '{{invoice_number}}' => $invoice->getNumber(),
449 '{{invoice_title}}' => $invoice->getTitle(),
450 '{{client_name}}' => $invoice->getCustomerName(),
451 '{{client_email}}' => $invoice->getCustomerEmail(),
452 '{{client_address}}' => $invoice->getCustomerAddress(),
453 '{{client_first_name}}' => $client ? $client->getFirstName() : '',
454 '{{client_last_name}}' => $client ? $client->getLastName() : '',
455 '{{company_name}}' => get_bloginfo('name'),
456 '{{company_email}}' => $this->settings['from_email'],
457 '{{company_phone}}' => get_option('easy_invoice_company_phone', ''),
458 '{{company_address}}' => get_option('easy_invoice_company_address', ''),
459 '{{company_website}}' => get_option('easy_invoice_company_website', ''),
460 '{{total_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTotal()),
461 '{{subtotal}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getSubtotal()),
462 '{{tax_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTaxAmount()),
463 '{{discount_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getDiscountAmount()),
464 '{{due_date}}' => date('F j, Y', strtotime($invoice->getDueDate())),
465 '{{issue_date}}' => date('F j, Y', strtotime($invoice->getIssueDate())),
466 '{{invoice_url}}' => $invoice_url, // Use correct (possibly secure) link
467 '{{payment_url}}' => add_query_arg('payment', '1', get_permalink($invoice->getId())),
468 '{{site_url}}' => get_site_url(),
469 '{{admin_url}}' => admin_url(),
470 '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
471 ], $additional_data);
472 }
473
474 /**
475 * Get quote replacements
476 *
477 * @param Quote $quote The quote
478 * @param array $additional_data Additional data
479 * @return array Replacements
480 */
481 private function getQuoteReplacements(Quote $quote, array $additional_data = []): array {
482 $currency_symbol = get_option('easy_invoice_currency_symbol', '$');
483
484 $quote_url = get_permalink($quote->getId());
485 $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
486 if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
487 $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId());
488 if ($secure_url) {
489 $quote_url = $secure_url;
490 }
491 }
492
493 // Get client data for additional fields
494 $client = null;
495 if ($quote->getClientId()) {
496 $client_repository = new \EasyInvoice\Repositories\ClientRepository();
497 $client = $client_repository->find($quote->getClientId());
498 }
499
500 return array_merge([
501 '{{quote_number}}' => $quote->getNumber(),
502 '{{quote_title}}' => $quote->getTitle(),
503 '{{client_name}}' => $quote->getCustomerName(),
504 '{{client_email}}' => $quote->getCustomerEmail(),
505 '{{client_address}}' => $quote->getCustomerAddress(),
506 '{{client_first_name}}' => $client ? $client->getFirstName() : '',
507 '{{client_last_name}}' => $client ? $client->getLastName() : '',
508 '{{company_name}}' => get_bloginfo('name'),
509 '{{company_email}}' => $this->settings['from_email'],
510 '{{company_phone}}' => get_option('easy_invoice_company_phone', ''),
511 '{{company_address}}' => get_option('easy_invoice_company_address', ''),
512 '{{company_website}}' => get_option('easy_invoice_company_website', ''),
513 '{{total_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTotal()),
514 '{{subtotal}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getSubtotal()),
515 '{{tax_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTaxAmount()),
516 '{{discount_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getDiscountAmount()),
517 '{{expiry_date}}' => date('F j, Y', strtotime($quote->getExpiryDate())),
518 '{{issue_date}}' => date('F j, Y', strtotime($quote->getIssueDate())),
519 '{{quote_url}}' => $quote_url,
520 '{{site_url}}' => get_site_url(),
521 '{{admin_url}}' => admin_url(),
522 '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
523 ], $additional_data);
524 }
525
526 /**
527 * Get payment replacements
528 *
529 * @param Invoice $invoice The invoice
530 * @param array $payment_data Payment data
531 * @return array Replacements
532 */
533 private function getPaymentReplacements(Invoice $invoice, array $payment_data = []): array {
534 $replacements = $this->getInvoiceReplacements($invoice, $payment_data);
535
536 // Add payment-specific replacements
537 $replacements['{{payment_amount}}'] = isset($payment_data['amount']) ? $this->formatCurrency($payment_data['amount']) : $this->formatCurrency($invoice->getTotal());
538 $replacements['{{payment_date}}'] = isset($payment_data['date']) ? $payment_data['date'] : current_time('Y-m-d');
539 $replacements['{{payment_method}}'] = isset($payment_data['method']) ? $payment_data['method'] : __('Online Payment', 'easy-invoice');
540 $replacements['{{transaction_id}}'] = isset($payment_data['transaction_id']) ? $payment_data['transaction_id'] : __('N/A', 'easy-invoice');
541 $replacements['{{acceptance_date}}'] = isset($payment_data['acceptance_date']) ? $payment_data['acceptance_date'] : current_time('Y-m-d');
542 $replacements['{{response_date}}'] = isset($payment_data['response_date']) ? $payment_data['response_date'] : current_time('Y-m-d');
543 $replacements['{{decline_reason}}'] = isset($payment_data['decline_reason']) ? $payment_data['decline_reason'] : __('No specific reason provided', 'easy-invoice');
544
545 return $replacements;
546 }
547
548 /**
549 * Process template with replacements
550 *
551 * @param string $template The template
552 * @param array $replacements The replacements
553 * @return string Processed template
554 */
555 private function processTemplate(string $template, array $replacements): string {
556 return easy_invoice_str_replace(array_keys($replacements), array_values($replacements), $template);
557 }
558
559 /**
560 * Prepare email headers
561 *
562 * @return array Headers
563 */
564 private function prepareEmailHeaders(): array {
565 $headers = [
566 'Content-Type: text/html; charset=UTF-8',
567 'From: ' . $this->settings['from_name'] . ' <' . $this->settings['from_email'] . '>',
568 ];
569
570 // Add Reply-To if set
571 if (!empty($this->settings['reply_to_email'])) {
572 $reply_to_name = !empty($this->settings['reply_to_name']) ? $this->settings['reply_to_name'] : $this->settings['from_name'];
573 $headers[] = 'Reply-To: ' . $reply_to_name . ' <' . $this->settings['reply_to_email'] . '>';
574 }
575
576 return $headers;
577 }
578
579 /**
580 * Wrap message in HTML template
581 *
582 * @param string $message The message
583 * @return string HTML wrapped message
584 */
585 private function wrapInHtmlTemplate(string $message): string {
586 $logo_html = '';
587 if (!empty($this->settings['email_logo'])) {
588 $logo_html = '<div style="text-align: center; margin-bottom: 40px;"><img src="' . esc_url($this->settings['email_logo']) . '" alt="' . esc_attr($this->settings['from_name']) . '" style="max-width: 200px; height: auto; border-radius: 8px;"></div>';
589 }
590
591 $footer_html = '';
592 if (!empty($this->settings['footer_text'])) {
593 $footer_html = '<div style="margin-top: 50px; padding-top: 25px; border-top: 2px solid #f3f4f6; font-size: 14px; color: #6b7280; text-align: center;">' . wpautop($this->settings['footer_text']) . '</div>';
594 }
595
596 return '
597 <!DOCTYPE html>
598 <html>
599 <head>
600 <meta charset="UTF-8">
601 <meta name="viewport" content="width=device-width, initial-scale=1.0">
602 <title>' . esc_html($this->settings['from_name']) . '</title>
603 <style>
604 body {
605 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
606 line-height: 1.6;
607 color: #374151;
608 margin: 0;
609 padding: 0;
610 background-color: #f9fafb;
611 }
612 .email-container {
613 max-width: 600px;
614 margin: 0 auto;
615 background-color: #ffffff;
616 border-radius: 12px;
617 box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
618 overflow: hidden;
619 }
620 .email-header {
621 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
622 padding: 50px 30px;
623 text-align: center;
624 position: relative;
625 }
626 .email-header::before {
627 content: "";
628 position: absolute;
629 top: 0;
630 left: 0;
631 right: 0;
632 bottom: 0;
633 background: url("data:image/svg+xml,%3Csvg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg"%3E%3Cg fill="none" fill-rule="evenodd"%3E%3Cg fill="%23ffffff" fill-opacity="0.1"%3E%3Ccircle cx="30" cy="30" r="2"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
634 opacity: 0.3;
635 }
636 .email-header h1 {
637 color: #ffffff;
638 margin: 0;
639 font-size: 28px;
640 font-weight: 700;
641 position: relative;
642 z-index: 1;
643 }
644 .email-content {
645 padding: 50px 40px;
646 background: #ffffff;
647 }
648 .email-content p {
649 margin: 0 0 20px 0;
650 color: #374151;
651 line-height: 1.7;
652 }
653 .email-content h2 {
654 color: #1f2937;
655 font-size: 28px;
656 font-weight: 700;
657 margin: 0 0 30px 0;
658 text-align: center;
659 }
660 .email-content h3 {
661 color: #374151;
662 font-size: 20px;
663 font-weight: 600;
664 margin: 0 0 16px 0;
665 }
666 .email-footer {
667 background-color: #f9fafb;
668 padding: 40px 30px;
669 text-align: center;
670 border-top: 1px solid #e5e7eb;
671 }
672 .email-footer p {
673 margin: 0;
674 color: #6b7280;
675 font-size: 14px;
676 }
677 .highlight-box {
678 background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
679 border-left: 4px solid #3b82f6;
680 padding: 30px;
681 margin: 30px 0;
682 border-radius: 0 12px 12px 0;
683 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
684 }
685 .highlight-box p {
686 margin: 0;
687 font-size: 16px;
688 line-height: 1.6;
689 }
690 .highlight-box strong {
691 color: #1f2937;
692 font-weight: 600;
693 }
694 .info-box {
695 background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
696 border: 1px solid #93c5fd;
697 border-radius: 12px;
698 padding: 25px;
699 margin: 30px 0;
700 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
701 }
702 .info-box p {
703 margin: 0;
704 color: #1e40af;
705 font-size: 15px;
706 line-height: 1.7;
707 }
708 .info-box strong {
709 color: #1e3a8a;
710 font-weight: 600;
711 }
712 .success-box {
713 background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
714 border: 1px solid #6ee7b7;
715 border-radius: 12px;
716 padding: 25px;
717 margin: 30px 0;
718 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
719 }
720 .success-box p {
721 margin: 0;
722 color: #065f46;
723 font-size: 15px;
724 line-height: 1.7;
725 }
726 .success-box strong {
727 color: #047857;
728 font-weight: 600;
729 }
730 .warning-box {
731 background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
732 border: 1px solid #f59e0b;
733 border-radius: 12px;
734 padding: 25px;
735 margin: 30px 0;
736 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
737 }
738 .warning-box p {
739 margin: 0;
740 color: #92400e;
741 font-size: 15px;
742 line-height: 1.7;
743 }
744 .warning-box strong {
745 color: #78350f;
746 font-weight: 600;
747 }
748 .button {
749 display: inline-block;
750 background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
751 color: #ffffff;
752 padding: 14px 28px;
753 text-decoration: none;
754 border-radius: 8px;
755 font-weight: 600;
756 margin: 20px 0;
757 box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3);
758 transition: all 0.2s ease;
759 }
760 .button:hover {
761 background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
762 transform: translateY(-1px);
763 box-shadow: 0 6px 8px -1px rgba(59, 130, 246, 0.4);
764 }
765 .divider {
766 height: 1px;
767 background: linear-gradient(90deg, transparent 0%, #e5e7eb 50%, transparent 100%);
768 margin: 40px 0;
769 }
770 .amount-highlight {
771 font-size: 28px;
772 font-weight: 700;
773 color: #059669;
774 text-align: center;
775 margin: 25px 0;
776 display: block;
777 }
778 .status-badge {
779 display: inline-block;
780 padding: 6px 12px;
781 border-radius: 20px;
782 font-size: 12px;
783 font-weight: 600;
784 text-transform: uppercase;
785 letter-spacing: 0.5px;
786 }
787 .status-paid {
788 background: #d1fae5;
789 color: #065f46;
790 }
791 .status-pending {
792 background: #fef3c7;
793 color: #92400e;
794 }
795 .status-overdue {
796 background: #fee2e2;
797 color: #991b1b;
798 }
799 @media only screen and (max-width: 600px) {
800 .email-content { padding: 25px 20px; }
801 .email-header { padding: 35px 20px; }
802 .email-header h1 { font-size: 24px; }
803 .email-content h2 { font-size: 22px; }
804 .highlight-box, .info-box, .success-box, .warning-box { padding: 20px; }
805 .amount-highlight { font-size: 24px; }
806 }
807 </style>
808 </head>
809 <body>
810 <div class="email-container">
811 ' . $logo_html . '
812 <div class="email-content">
813 ' . wpautop($message) . '
814 </div>
815 ' . $footer_html . '
816 </div>
817 </body>
818 </html>';
819 }
820
821 /**
822 * Register email settings
823 */
824 public function registerEmailSettings(): void {
825 // Email settings section
826 add_settings_section(
827 'easy_invoice_email_settings',
828 __('Email Configuration', 'easy-invoice'),
829 [$this, 'emailSettingsSectionCallback'],
830 'easy_invoice_settings'
831 );
832
833 // Register settings
834 register_setting('easy_invoice_settings', 'easy_invoice_email_from_name');
835 register_setting('easy_invoice_settings', 'easy_invoice_email_from_address');
836 register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to');
837 register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to_name');
838 register_setting('easy_invoice_settings', 'easy_invoice_enable_email_styling');
839 register_setting('easy_invoice_settings', 'easy_invoice_email_logo');
840 register_setting('easy_invoice_settings', 'easy_invoice_email_footer_text');
841 register_setting('easy_invoice_settings', 'easy_invoice_bcc_admin');
842 register_setting('easy_invoice_settings', 'easy_invoice_admin_email');
843
844 // Add settings fields
845 add_settings_field(
846 'easy_invoice_email_from_name',
847 __('From Name', 'easy-invoice'),
848 [$this, 'textFieldCallback'],
849 'easy_invoice_settings',
850 'easy_invoice_email_settings',
851 ['label_for' => 'easy_invoice_email_from_name']
852 );
853
854 add_settings_field(
855 'easy_invoice_email_from_address',
856 __('From Email Address', 'easy-invoice'),
857 [$this, 'emailFieldCallback'],
858 'easy_invoice_settings',
859 'easy_invoice_email_settings',
860 ['label_for' => 'easy_invoice_email_from_address']
861 );
862
863 add_settings_field(
864 'easy_invoice_email_reply_to',
865 __('Reply-To Email', 'easy-invoice'),
866 [$this, 'emailFieldCallback'],
867 'easy_invoice_settings',
868 'easy_invoice_email_settings',
869 ['label_for' => 'easy_invoice_email_reply_to']
870 );
871
872 add_settings_field(
873 'easy_invoice_enable_email_styling',
874 __('Enable HTML Emails', 'easy-invoice'),
875 [$this, 'checkboxFieldCallback'],
876 'easy_invoice_settings',
877 'easy_invoice_email_settings',
878 ['label_for' => 'easy_invoice_enable_email_styling']
879 );
880
881 add_settings_field(
882 'easy_invoice_bcc_admin',
883 __('BCC Admin on All Emails', 'easy-invoice'),
884 [$this, 'checkboxFieldCallback'],
885 'easy_invoice_settings',
886 'easy_invoice_email_settings',
887 ['label_for' => 'easy_invoice_bcc_admin']
888 );
889 }
890
891 /**
892 * Add email settings section
893 *
894 * @param array $sections Settings sections
895 * @return array Modified sections
896 */
897 public function addEmailSettingsSection(array $sections): array {
898 $sections['email'] = [
899 'title' => __('Email Settings', 'easy-invoice'),
900 'description' => __('Configure email sending options and templates', 'easy-invoice'),
901 'icon' => 'fas fa-envelope',
902 'fields' => [
903 'easy_invoice_email_from_name' => [
904 'label' => __('From Name', 'easy-invoice'),
905 'type' => 'text',
906 'default' => get_bloginfo('name'),
907 'col_span' => 'sm:col-span-3'
908 ],
909 'easy_invoice_email_from_address' => [
910 'label' => __('From Email Address', 'easy-invoice'),
911 'type' => 'email',
912 'default' => get_bloginfo('admin_email'),
913 'col_span' => 'sm:col-span-3'
914 ],
915 'easy_invoice_email_reply_to' => [
916 'label' => __('Reply-To Email', 'easy-invoice'),
917 'type' => 'email',
918 'default' => '',
919 'col_span' => 'sm:col-span-3'
920 ],
921 'easy_invoice_enable_email_styling' => [
922 'label' => __('Enable HTML Emails', 'easy-invoice'),
923 'type' => 'checkbox',
924 'default' => 'yes',
925 'col_span' => 'sm:col-span-3'
926 ],
927 'easy_invoice_bcc_admin' => [
928 'label' => __('BCC Admin on All Emails', 'easy-invoice'),
929 'type' => 'checkbox',
930 'default' => 'no',
931 'col_span' => 'sm:col-span-3'
932 ],
933 'easy_invoice_email_logo' => [
934 'label' => __('Email Logo URL', 'easy-invoice'),
935 'type' => 'url',
936 'default' => '',
937 'col_span' => 'sm:col-span-6'
938 ],
939 'easy_invoice_email_footer_text' => [
940 'label' => __('Email Footer Text', 'easy-invoice'),
941 'type' => 'textarea',
942 'default' => '',
943 'col_span' => 'sm:col-span-6'
944 ],
945 ]
946 ];
947
948 return $sections;
949 }
950
951 /**
952 * Email settings section callback
953 */
954 public function emailSettingsSectionCallback(): void {
955 echo '<p>' . __('Configure how emails are sent from Easy Invoice.', 'easy-invoice') . '</p>';
956 }
957
958 /**
959 * Text field callback
960 *
961 * @param array $args Field arguments
962 */
963 public function textFieldCallback(array $args): void {
964 $field_id = $args['label_for'];
965 $value = get_option($field_id, '');
966 echo '<input type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($value) . '" class="regular-text">';
967 }
968
969 /**
970 * Email field callback
971 *
972 * @param array $args Field arguments
973 */
974 public function emailFieldCallback(array $args): void {
975 $field_id = $args['label_for'];
976 $value = get_option($field_id, '');
977 echo '<input type="email" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($value) . '" class="regular-text">';
978 }
979
980 /**
981 * Checkbox field callback
982 *
983 * @param array $args Field arguments
984 */
985 public function checkboxFieldCallback(array $args): void {
986 $field_id = $args['label_for'];
987 $value = get_option($field_id, '');
988 echo '<input type="checkbox" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="yes"' . checked($value, 'yes', false) . '>';
989 echo '<span class="description">' . __('Enable this option', 'easy-invoice') . '</span>';
990 }
991
992 /**
993 * Log email sent
994 *
995 * @param Invoice $invoice The invoice
996 * @param string $email The email address
997 * @param string $type The email type
998 */
999 public function logEmailSent($invoice, string $email, string $type): void {
1000 $this->log(sprintf('Email sent to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'info');
1001 }
1002
1003 /**
1004 * Log email failed
1005 *
1006 * @param Invoice $invoice The invoice
1007 * @param string $email The email address
1008 * @param string $type The email type
1009 */
1010 public function logEmailFailed($invoice, string $email, string $type): void {
1011 $this->log(sprintf('Email failed to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'error');
1012 }
1013
1014 /**
1015 * Get default invoice template
1016 *
1017 * @return string Template
1018 */
1019 private function getDefaultInvoiceTemplate(): string {
1020 return '<h2>📄 Your Invoice is Ready</h2>
1021
1022 <p>Dear {{client_name}},</p>
1023
1024 <div class="highlight-box">
1025 <p><strong>Invoice #{{invoice_number}}</strong><br>
1026 <span class="amount-highlight">{{total_amount}}</span><br>
1027 Due Date: <strong>{{due_date}}</strong></p>
1028 </div>
1029
1030 <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>
1031
1032 <div class="info-box">
1033 <p><strong>📋 Payment Details:</strong><br>
1034 • Invoice Number: {{invoice_number}}<br>
1035 • Total Amount: {{total_amount}}<br>
1036 • Due Date: {{due_date}}<br>
1037 • Payment Terms: {{payment_terms}}</p>
1038 </div>
1039
1040 <div class="highlight-box">
1041 <p><strong>🔗 View Invoice Online:</strong><br>
1042 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1043 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1044 </div>
1045
1046 <div class="warning-box">
1047 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1048 </div>
1049
1050 <p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1051
1052 <div class="divider"></div>
1053
1054 <p>Thank you for your business!</p>
1055
1056 <p>Best regards,<br>
1057 <strong>{{company_name}}</strong><br>
1058 {{company_email}}</p>';
1059 }
1060
1061 private function getDefaultReminderTemplate(): string {
1062 return '<h2>⏰ Payment Reminder</h2>
1063
1064 <p>Dear {{client_name}},</p>
1065
1066 <div class="warning-box">
1067 <p><strong>Invoice #{{invoice_number}}</strong><br>
1068 <span class="amount-highlight">{{total_amount}}</span><br>
1069 Due Date: <strong>{{due_date}}</strong></p>
1070 </div>
1071
1072 <p>This is a friendly reminder that payment for the above invoice is now due. If you have already made the payment, please disregard this message.</p>
1073
1074 <div class="info-box">
1075 <p><strong>💳 Payment Options:</strong><br>
1076 • Online payment through our secure portal<br>
1077 • Bank transfer to the details provided<br>
1078 • Check or money order</p>
1079 </div>
1080
1081 <div class="highlight-box">
1082 <p><strong>🔗 View Invoice Online:</strong><br>
1083 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1084 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1085 </div>
1086
1087 <div class="highlight-box">
1088 <p><strong>📞 Need Help?</strong> If you have any questions or need to discuss payment arrangements, please contact us immediately.</p>
1089 </div>
1090
1091 <p>Thank you for your prompt attention to this matter.</p>
1092
1093 <div class="divider"></div>
1094
1095 <p>Best regards,<br>
1096 <strong>{{company_name}}</strong><br>
1097 {{company_email}}</p>';
1098 }
1099
1100 private function getDefaultPaymentTemplate(): string {
1101 return '<h2>�
1102 Payment Received - Thank You!</h2>
1103
1104 <p>Dear {{client_name}},</p>
1105
1106 <div class="success-box">
1107 <p><strong>Payment Confirmation</strong><br>
1108 Invoice #{{invoice_number}}<br>
1109 <span class="amount-highlight">{{payment_amount}}</span><br>
1110 Payment Date: <strong>{{payment_date}}</strong><br>
1111 Payment Method: <strong>{{payment_method}}</strong></p>
1112 </div>
1113
1114 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1115
1116 <div class="info-box">
1117 <p><strong>📊 Payment Details:</strong><br>
1118 Invoice Number: {{invoice_number}}<br>
1119 Amount Paid: {{payment_amount}}<br>
1120 Payment Date: {{payment_date}}<br>
1121 Payment Method: {{payment_method}}<br>
1122 Transaction ID: {{transaction_id}}</p>
1123 </div>
1124
1125 <div class="highlight-box">
1126 <p><strong>🎉 Status: PAID</strong><br>
1127 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1128 </div>
1129
1130 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1131
1132 <div class="divider"></div>
1133
1134 <p>Thank you for choosing our services!</p>
1135
1136 <p>Best regards,<br>
1137 <strong>{{company_name}}</strong><br>
1138 {{company_email}}</p>';
1139 }
1140
1141 private function getDefaultQuoteTemplate(): string {
1142 return '<h2>📋 Your Quote is Ready</h2>
1143
1144 <p>Dear {{client_name}},</p>
1145
1146 <div class="highlight-box">
1147 <p><strong>Quote #{{quote_number}}</strong><br>
1148 <span class="amount-highlight">{{total_amount}}</span><br>
1149 Valid Until: <strong>{{expiry_date}}</strong></p>
1150 </div>
1151
1152 <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>
1153
1154 <div class="info-box">
1155 <p><strong>📋 Quote Summary:</strong><br>
1156 • Quote Number: {{quote_number}}<br>
1157 • Total Amount: {{total_amount}}<br>
1158 • Valid Until: {{expiry_date}}<br>
1159 • Terms: {{payment_terms}}</p>
1160 </div>
1161
1162 <div class="highlight-box">
1163 <p><strong>🔗 View Quote Online:</strong><br>
1164 <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1165 <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
1166 </div>
1167
1168 <div class="warning-box">
1169 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1170 </div>
1171
1172 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1173
1174 <div class="divider"></div>
1175
1176 <p>We look forward to working with you!</p>
1177
1178 <p>Best regards,<br>
1179 <strong>{{company_name}}</strong><br>
1180 {{company_email}}</p>';
1181 }
1182
1183 private function getDefaultQuoteAcceptedTemplate(): string {
1184 return '<h2>🎉 Quote Accepted - Project Confirmed!</h2>
1185
1186 <p>Dear {{client_name}},</p>
1187
1188 <div class="success-box">
1189 <p><strong>Quote #{{quote_number}} - ACCEPTED</strong><br>
1190 <span class="amount-highlight">{{total_amount}}</span><br>
1191 Acceptance Date: <strong>{{acceptance_date}}</strong></p>
1192 </div>
1193
1194 <p>Thank you for accepting our quote! We\'re excited to begin working on your project.</p>
1195
1196 <div class="info-box">
1197 <p><strong>🚀 Next Steps:</strong><br>
1198 • We will create an invoice for the accepted quote<br>
1199 • You will receive payment instructions<br>
1200 • Project work will begin as scheduled</p>
1201 </div>
1202
1203 <div class="highlight-box">
1204 <p><strong>📞 What\'s Next?</strong> Our team will be in touch shortly with the next steps and any additional information you may need.</p>
1205 </div>
1206
1207 <div class="divider"></div>
1208
1209 <p>Thank you for choosing our services!</p>
1210
1211 <p>Best regards,<br>
1212 <strong>{{company_name}}</strong><br>
1213 {{company_email}}</p>';
1214 }
1215
1216 private function getDefaultQuoteDeclinedTemplate(): string {
1217 return '<h2>📝 Quote Response Received</h2>
1218
1219 <p>Dear {{client_name}},</p>
1220
1221 <div class="warning-box">
1222 <p><strong>Quote #{{quote_number}} - DECLINED</strong><br>
1223 Response Date: <strong>{{response_date}}</strong></p>
1224 </div>
1225
1226 <p>We have received your response regarding our quote. We understand that this quote may not have met your current needs.</p>
1227
1228 <div class="info-box">
1229 <p><strong>📋 Feedback:</strong><br>
1230 • Reason: {{decline_reason}}<br>
1231 • Response Date: {{response_date}}</p>
1232 </div>
1233
1234 <div class="highlight-box">
1235 <p><strong>🤝 Future Opportunities:</strong> We appreciate you taking the time to review our proposal. If your requirements change in the future, we would be happy to discuss new opportunities.</p>
1236 </div>
1237
1238 <div class="divider"></div>
1239
1240 <p>Thank you for considering our services!</p>
1241
1242 <p>Best regards,<br>
1243 <strong>{{company_name}}</strong><br>
1244 {{company_email}}</p>';
1245 }
1246
1247 /**
1248 * Refresh settings and templates
1249 * Call this method when settings are updated
1250 */
1251 public function refreshSettings(): void {
1252 $this->loadSettings();
1253 $this->loadTemplates();
1254 }
1255
1256 /**
1257 * Get email templates
1258 *
1259 * @return array Templates
1260 */
1261 public function getTemplates(): array {
1262 return $this->templates;
1263 }
1264
1265 /**
1266 * Get email settings
1267 *
1268 * @return array Settings
1269 */
1270 public function getSettings(): array {
1271 return $this->settings;
1272 }
1273
1274 /**
1275 * Test email functionality
1276 *
1277 * @param string $to_email Email to send test to
1278 * @return array Result
1279 */
1280 public function testEmail(string $to_email): array {
1281 $subject = 'Easy Invoice - Email Configuration Test';
1282 $message = '<h2>🧪 Email Configuration Test</h2>
1283
1284 <p>Hello!</p>
1285
1286 <div class="success-box">
1287 <p><strong>�
1288 Test Email Successfully Sent</strong><br>
1289 Date: <strong>' . current_time('Y-m-d H:i:s') . '</strong><br>
1290 To: <strong>' . esc_html($to_email) . '</strong></p>
1291 </div>
1292
1293 <p>This is a test email to verify that your Easy Invoice email configuration is working correctly.</p>
1294
1295 <div class="info-box">
1296 <p><strong>⚙️ Email Settings Verified:</strong><br>
1297 • From Name: ' . esc_html($this->settings['from_name']) . '<br>
1298 • From Email: ' . esc_html($this->settings['from_email']) . '<br>
1299 • Reply-To: ' . esc_html($this->settings['reply_to_email'] ?: 'Not set') . '<br>
1300 • HTML Emails: ' . ($this->settings['enable_html'] === 'yes' ? 'Enabled' : 'Disabled') . '</p>
1301 </div>
1302
1303 <div class="highlight-box">
1304 <p><strong>🎉 Congratulations!</strong> If you received this email, your email configuration is working properly and you can now send invoices, quotes, and payment confirmations to your clients.</p>
1305 </div>
1306
1307 <div class="divider"></div>
1308
1309 <p>Thank you for using Easy Invoice!</p>
1310
1311 <p>Best regards,<br>
1312 <strong>' . esc_html($this->settings['from_name']) . '</strong></p>';
1313
1314 if ($this->settings['enable_html'] === 'yes') {
1315 $message = $this->wrapInHtmlTemplate($message);
1316 }
1317
1318 $headers = $this->prepareEmailHeaders();
1319
1320 $sent = $this->sendEmail($to_email, $subject, $message, $headers);
1321
1322 if ($sent) {
1323 return ['success' => true, 'message' => __('Test email sent successfully', 'easy-invoice')];
1324 } else {
1325 return ['success' => false, 'message' => __('Failed to send test email', 'easy-invoice')];
1326 }
1327 }
1328
1329 /**
1330 * Send test template email with custom subject and body
1331 *
1332 * @param string $to_email Email to send test to
1333 * @param string $subject Email subject
1334 * @param string $body Email body
1335 * @return array Result
1336 */
1337 public function sendTestTemplateEmail(string $to_email, string $subject, string $body): array {
1338 if ($this->settings['enable_html'] === 'yes') {
1339 $body = $this->wrapInHtmlTemplate($body);
1340 }
1341
1342 $headers = $this->prepareEmailHeaders();
1343
1344 $sent = $this->sendEmail($to_email, $subject, $body, $headers);
1345
1346 if ($sent) {
1347 return ['success' => true, 'message' => __('Template test email sent successfully', 'easy-invoice')];
1348 } else {
1349 return ['success' => false, 'message' => __('Failed to send template test email', 'easy-invoice')];
1350 }
1351 }
1352
1353 /**
1354 * Send payment received email
1355 *
1356 * @param Invoice $invoice The invoice
1357 * @param array $payment_data Payment data
1358 * @return array Result array with success status and message
1359 */
1360 public function sendPaymentEmail(Invoice $invoice, array $payment_data = []): array {
1361 try {
1362 // Validate invoice
1363 if (!$invoice || !$invoice->getId()) {
1364 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1365 }
1366
1367 // Get client email
1368 $client_email = $invoice->getCustomerEmail();
1369 if (empty($client_email)) {
1370 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
1371 }
1372
1373 // Get template
1374 $template_key = 'invoice_paid';
1375 if (!isset($this->templates[$template_key])) {
1376 return ['success' => false, 'message' => __('Payment email template not found', 'easy-invoice')];
1377 }
1378
1379 $template = $this->templates[$template_key];
1380
1381 // Check if email is enabled
1382 if (!$template['enabled']) {
1383 return ['success' => false, 'message' => __('Payment received email is disabled', 'easy-invoice')];
1384 }
1385
1386 // Prepare email data
1387 $email_data = $this->preparePaymentEmailData($invoice, $template, $payment_data);
1388
1389 // Send email
1390 $sent = $this->sendEmail(
1391 $email_data['to'],
1392 $email_data['subject'],
1393 $email_data['message'],
1394 $email_data['headers']
1395 );
1396
1397 if ($sent) {
1398 // Log success
1399 do_action('easy_invoice_payment_email_sent', $invoice, $client_email, $payment_data);
1400
1401 return [
1402 'success' => true,
1403 'message' => __('Payment email sent successfully', 'easy-invoice'),
1404 'email_data' => $email_data
1405 ];
1406 } else {
1407 // Log failure
1408 do_action('easy_invoice_payment_email_failed', $invoice, $client_email, $payment_data);
1409
1410 return ['success' => false, 'message' => __('Failed to send payment email', 'easy-invoice')];
1411 }
1412
1413 } catch (\Exception $e) {
1414 $this->log('Payment email sending error: ' . $e->getMessage(), 'error');
1415 return ['success' => false, 'message' => __('Error sending payment email: ', 'easy-invoice') . $e->getMessage()];
1416 }
1417 }
1418
1419 /**
1420 * Send admin notification when payment is received
1421 *
1422 * @param Invoice $invoice The invoice
1423 * @param array $payment_data Payment data (method, amount, etc.)
1424 * @return array Result array with success status and message
1425 */
1426 public function sendAdminPaymentNotification(Invoice $invoice, array $payment_data = []): array {
1427 try {
1428 // Validate invoice
1429 if (!$invoice || !$invoice->getId()) {
1430 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1431 }
1432
1433 // Get admin email
1434 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
1435 if (empty($admin_email)) {
1436 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
1437 }
1438
1439 // Get payment method
1440 $payment_method = $payment_data['payment_method'] ?? $payment_data['method'] ?? 'online';
1441 $payment_method_label = $this->getPaymentMethodLabel($payment_method);
1442
1443 // Format amount
1444 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($invoice);
1445 $amount = $formatter->format($invoice->getTotal());
1446
1447 // Prepare email subject
1448 $subject = sprintf(
1449 __('New Payment Received - Invoice #%s', 'easy-invoice'),
1450 $invoice->getNumber()
1451 );
1452
1453 // Prepare email message
1454 $message = $this->prepareAdminPaymentNotificationMessage($invoice, $payment_method_label, $amount, $payment_data);
1455
1456 // Add HTML wrapper if enabled
1457 if ($this->settings['enable_html'] === 'yes') {
1458 $message = $this->wrapInHtmlTemplate($message);
1459 }
1460
1461 // Prepare headers
1462 $headers = $this->prepareEmailHeaders();
1463
1464 // Send email
1465 $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
1466
1467 if ($sent) {
1468 do_action('easy_invoice_admin_payment_notification_sent', $invoice, $admin_email, $payment_data);
1469 return [
1470 'success' => true,
1471 'message' => __('Admin notification sent successfully', 'easy-invoice')
1472 ];
1473 } else {
1474 do_action('easy_invoice_admin_payment_notification_failed', $invoice, $admin_email, $payment_data);
1475 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
1476 }
1477
1478 } catch (\Exception $e) {
1479 $this->log('Admin payment notification error: ' . $e->getMessage(), 'error');
1480 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
1481 }
1482 }
1483
1484 /**
1485 * Send payment confirmation email to customer
1486 *
1487 * @param Invoice $invoice The invoice
1488 * @param array $payment_data Payment data
1489 * @return array Result array with success status and message
1490 */
1491 public function sendPaymentConfirmationEmail(Invoice $invoice, array $payment_data = []): array {
1492 try {
1493 // Validate invoice
1494 if (!$invoice || !$invoice->getId()) {
1495 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1496 }
1497
1498 // Get customer email
1499 $customer_email = $invoice->getCustomerEmail();
1500 if (empty($customer_email)) {
1501 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
1502 }
1503
1504 // Get currency settings
1505 $settings_controller = new \EasyInvoice\Controllers\SettingsController();
1506 $settings = $settings_controller->getSettings();
1507 $currency_code = $settings['easy_invoice_currency_code'] ?? 'USD';
1508 $currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol($currency_code);
1509
1510 // Format amount
1511 $amount = $invoice->getTotal();
1512 $formatted_amount = $currency_symbol . number_format($amount, 2);
1513
1514 // Prepare email subject
1515 $site_name = get_bloginfo('name');
1516 $subject = sprintf(
1517 __('[%s] Payment Confirmed - Invoice #%s', 'easy-invoice'),
1518 $site_name,
1519 $invoice->getNumber()
1520 );
1521
1522 // Prepare email message
1523 $message = $this->preparePaymentConfirmationMessage($invoice, $formatted_amount);
1524
1525 // Add HTML wrapper if enabled
1526 if ($this->settings['enable_html'] === 'yes') {
1527 $message = $this->wrapInHtmlTemplate($message);
1528 }
1529
1530 // Prepare headers
1531 $headers = $this->prepareEmailHeaders();
1532
1533 // Add BCC to admin if enabled (but skip if this is from payment completion hook to avoid duplicate)
1534 // The payment completion hook already sends a dedicated admin notification
1535 $skip_bcc = isset($payment_data['skip_bcc']) && $payment_data['skip_bcc'] === true;
1536 if (!$skip_bcc && $this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
1537 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
1538 }
1539
1540 // Send email
1541 $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
1542
1543 if ($sent) {
1544 do_action('easy_invoice_payment_confirmation_sent', $invoice, $customer_email, $payment_data);
1545 return [
1546 'success' => true,
1547 'message' => __('Payment confirmation email sent successfully', 'easy-invoice')
1548 ];
1549 } else {
1550 do_action('easy_invoice_payment_confirmation_failed', $invoice, $customer_email, $payment_data);
1551 return ['success' => false, 'message' => __('Failed to send payment confirmation email', 'easy-invoice')];
1552 }
1553
1554 } catch (\Exception $e) {
1555 $this->log('Payment confirmation email error: ' . $e->getMessage(), 'error');
1556 return ['success' => false, 'message' => __('Error sending payment confirmation: ', 'easy-invoice') . $e->getMessage()];
1557 }
1558 }
1559
1560 /**
1561 * Send payment rejection email to customer
1562 *
1563 * @param Invoice $invoice The invoice
1564 * @param string $reason Rejection reason
1565 * @return array Result array with success status and message
1566 */
1567 public function sendPaymentRejectionEmail(Invoice $invoice, string $reason = ''): array {
1568 try {
1569 // Validate invoice
1570 if (!$invoice || !$invoice->getId()) {
1571 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1572 }
1573
1574 // Get customer email
1575 $customer_email = $invoice->getCustomerEmail();
1576 if (empty($customer_email)) {
1577 return ['success' => false, 'message' => __('Customer email is missing', 'easy-invoice')];
1578 }
1579
1580 // Prepare email subject
1581 $site_name = get_bloginfo('name');
1582 $subject = sprintf(
1583 __('[%s] Payment Rejected - Invoice #%s', 'easy-invoice'),
1584 $site_name,
1585 $invoice->getNumber()
1586 );
1587
1588 // Prepare email message
1589 $message = $this->preparePaymentRejectionMessage($invoice, $reason);
1590
1591 // Add HTML wrapper if enabled
1592 if ($this->settings['enable_html'] === 'yes') {
1593 $message = $this->wrapInHtmlTemplate($message);
1594 }
1595
1596 // Prepare headers
1597 $headers = $this->prepareEmailHeaders();
1598
1599 // Add BCC to admin if enabled
1600 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
1601 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
1602 }
1603
1604 // Send email
1605 $sent = $this->sendEmail($customer_email, $subject, $message, $headers);
1606
1607 if ($sent) {
1608 do_action('easy_invoice_payment_rejection_sent', $invoice, $customer_email, $reason);
1609 return [
1610 'success' => true,
1611 'message' => __('Payment rejection email sent successfully', 'easy-invoice')
1612 ];
1613 } else {
1614 do_action('easy_invoice_payment_rejection_failed', $invoice, $customer_email, $reason);
1615 return ['success' => false, 'message' => __('Failed to send payment rejection email', 'easy-invoice')];
1616 }
1617
1618 } catch (\Exception $e) {
1619 $this->log('Payment rejection email error: ' . $e->getMessage(), 'error');
1620 return ['success' => false, 'message' => __('Error sending payment rejection: ', 'easy-invoice') . $e->getMessage()];
1621 }
1622 }
1623
1624 /**
1625 * Prepare admin payment notification message
1626 *
1627 * @param Invoice $invoice The invoice
1628 * @param string $payment_method_label Payment method label
1629 * @param string $amount Formatted amount
1630 * @param array $payment_data Payment data
1631 * @return string Email message
1632 */
1633 private function prepareAdminPaymentNotificationMessage(Invoice $invoice, string $payment_method_label, string $amount, array $payment_data = []): string {
1634 $invoice_number = $invoice->getNumber();
1635 $customer_name = $invoice->getCustomerName();
1636 $customer_email = $invoice->getCustomerEmail();
1637 $invoice_id = $invoice->getId();
1638
1639 $message = sprintf(
1640 __('A new %s payment has been received for invoice #%s.', 'easy-invoice'),
1641 $payment_method_label,
1642 $invoice_number
1643 );
1644 $message .= "\n\n";
1645 $message .= __('Invoice Details:', 'easy-invoice');
1646 $message .= "\n";
1647 $message .= sprintf(__('- Amount: %s', 'easy-invoice'), $amount);
1648 $message .= "\n";
1649 $message .= sprintf(__('- Customer: %s', 'easy-invoice'), $customer_name);
1650 $message .= "\n";
1651 $message .= sprintf(__('- Email: %s', 'easy-invoice'), $customer_email);
1652
1653 // Add transaction ID if available
1654 if (!empty($payment_data['transaction_id'])) {
1655 $message .= "\n";
1656 $message .= sprintf(__('- Transaction ID: %s', 'easy-invoice'), $payment_data['transaction_id']);
1657 }
1658
1659 $message .= "\n\n";
1660 $message .= __('Please review this payment in the admin dashboard:', 'easy-invoice');
1661 $message .= "\n";
1662 $message .= admin_url('admin.php?page=easy-invoice-payments&action=verify&invoice_id=' . $invoice_id);
1663 $message .= "\n\n";
1664 $message .= __('This is an automated message from Easy Invoice.', 'easy-invoice');
1665
1666 return $message;
1667 }
1668
1669 /**
1670 * Prepare payment confirmation message
1671 *
1672 * @param Invoice $invoice The invoice
1673 * @param string $formatted_amount Formatted amount
1674 * @return string Email message
1675 */
1676 private function preparePaymentConfirmationMessage(Invoice $invoice, string $formatted_amount): string {
1677 $customer_name = $invoice->getCustomerName();
1678 $invoice_number = $invoice->getNumber();
1679 $site_name = get_bloginfo('name');
1680 $company_name = get_option('easy_invoice_company_name', $site_name);
1681
1682 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
1683 $message .= "\n\n";
1684 $message .= sprintf(
1685 __('We are pleased to confirm that your payment of %s for Invoice #%s has been received and processed successfully.', 'easy-invoice'),
1686 $formatted_amount,
1687 $invoice_number
1688 );
1689 $message .= "\n\n";
1690 $message .= __('Thank you for your business.', 'easy-invoice');
1691 $message .= "\n\n";
1692 $message .= __('Regards,', 'easy-invoice');
1693 $message .= "\n";
1694 $message .= $company_name;
1695
1696 return $message;
1697 }
1698
1699 /**
1700 * Prepare payment rejection message
1701 *
1702 * @param Invoice $invoice The invoice
1703 * @param string $reason Rejection reason
1704 * @return string Email message
1705 */
1706 private function preparePaymentRejectionMessage(Invoice $invoice, string $reason = ''): string {
1707 $customer_name = $invoice->getCustomerName();
1708 $invoice_number = $invoice->getNumber();
1709 $site_name = get_bloginfo('name');
1710 $company_name = get_option('easy_invoice_company_name', $site_name);
1711
1712 $message = sprintf(__('Dear %s,', 'easy-invoice'), $customer_name);
1713 $message .= "\n\n";
1714 $message .= sprintf(
1715 __('We regret to inform you that your payment for Invoice #%s has been rejected.', 'easy-invoice'),
1716 $invoice_number
1717 );
1718
1719 if (!empty($reason)) {
1720 $message .= "\n\n";
1721 $message .= __('Reason:', 'easy-invoice');
1722 $message .= "\n";
1723 $message .= $reason;
1724 }
1725
1726 $message .= "\n\n";
1727 $message .= __('Please contact us if you have any questions or concerns.', 'easy-invoice');
1728 $message .= "\n\n";
1729 $message .= __('Regards,', 'easy-invoice');
1730 $message .= "\n";
1731 $message .= $company_name;
1732
1733 return $message;
1734 }
1735
1736 /**
1737 * Send admin notification for quote acceptance/decline
1738 *
1739 * @param Quote $quote The quote
1740 * @param string $action Action type ('accepted' or 'declined')
1741 * @return array Result array with success status and message
1742 */
1743 public function sendAdminQuoteNotification(Quote $quote, string $action = 'accepted'): array {
1744 try {
1745 // Validate quote
1746 if (!$quote || !$quote->getId()) {
1747 return ['success' => false, 'message' => __('Invalid quote', 'easy-invoice')];
1748 }
1749
1750 // Get admin email
1751 $admin_email = $this->settings['admin_email'] ?? get_option('admin_email');
1752 if (empty($admin_email)) {
1753 return ['success' => false, 'message' => __('Admin email is missing', 'easy-invoice')];
1754 }
1755
1756 // Prepare email subject
1757 $subject = sprintf(
1758 __('Quote %s has been %s', 'easy-invoice'),
1759 $quote->getNumber(),
1760 $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice')
1761 );
1762
1763 // Prepare email message
1764 $message = $this->prepareAdminQuoteNotificationMessage($quote, $action);
1765
1766 // Add HTML wrapper if enabled
1767 if ($this->settings['enable_html'] === 'yes') {
1768 $message = $this->wrapInHtmlTemplate($message);
1769 }
1770
1771 // Prepare headers
1772 $headers = $this->prepareEmailHeaders();
1773
1774 // Send email
1775 $sent = $this->sendEmail($admin_email, $subject, $message, $headers);
1776
1777 if ($sent) {
1778 do_action('easy_invoice_admin_quote_notification_sent', $quote, $admin_email, $action);
1779 return [
1780 'success' => true,
1781 'message' => __('Admin notification sent successfully', 'easy-invoice')
1782 ];
1783 } else {
1784 do_action('easy_invoice_admin_quote_notification_failed', $quote, $admin_email, $action);
1785 return ['success' => false, 'message' => __('Failed to send admin notification', 'easy-invoice')];
1786 }
1787
1788 } catch (\Exception $e) {
1789 $this->log('Admin quote notification error: ' . $e->getMessage(), 'error');
1790 return ['success' => false, 'message' => __('Error sending admin notification: ', 'easy-invoice') . $e->getMessage()];
1791 }
1792 }
1793
1794 /**
1795 * Prepare admin quote notification message
1796 *
1797 * @param Quote $quote The quote
1798 * @param string $action Action type ('accepted' or 'declined')
1799 * @return string Email message
1800 */
1801 private function prepareAdminQuoteNotificationMessage(Quote $quote, string $action): string {
1802 $site_name = get_bloginfo('name');
1803 $quote_number = $quote->getNumber();
1804 $customer_name = $quote->getCustomerName();
1805
1806 // Format amount
1807 $formatter = new \EasyInvoice\Helpers\InvoiceFormatter($quote);
1808 $formatted_amount = $formatter->format($quote->getTotal());
1809
1810 $action_label = $action === 'accepted' ? __('accepted', 'easy-invoice') : __('declined', 'easy-invoice');
1811 $date_label = $action === 'accepted' ? __('Accepted Date', 'easy-invoice') : __('Declined Date', 'easy-invoice');
1812
1813 $message = __('Hello,', 'easy-invoice');
1814 $message .= "\n\n";
1815 $message .= sprintf(
1816 __('The quote %s for %s has been %s by the client.', 'easy-invoice'),
1817 $quote_number,
1818 $customer_name,
1819 $action_label
1820 );
1821 $message .= "\n\n";
1822 $message .= __('Quote Details:', 'easy-invoice');
1823 $message .= "\n";
1824 $message .= sprintf(__('- Quote Number: %s', 'easy-invoice'), $quote_number);
1825 $message .= "\n";
1826 $message .= sprintf(__('- Client: %s', 'easy-invoice'), $customer_name);
1827 $message .= "\n";
1828 $message .= sprintf(__('- Total Amount: %s', 'easy-invoice'), $formatted_amount);
1829 $message .= "\n";
1830 $message .= sprintf(__('- %s: %s', 'easy-invoice'), $date_label, date_i18n(get_option('date_format') . ' ' . get_option('time_format')));
1831 $message .= "\n\n";
1832 $message .= __('You can view the quote at:', 'easy-invoice');
1833 $message .= "\n";
1834 $message .= get_permalink($quote->getId());
1835 $message .= "\n\n";
1836 $message .= __('Best regards,', 'easy-invoice');
1837 $message .= "\n";
1838 $message .= $site_name;
1839
1840 return $message;
1841 }
1842
1843 /**
1844 * Handle payment completed hook
1845 * Sends admin notification and customer confirmation when payment is completed
1846 *
1847 * @param int $invoice_id Invoice ID
1848 * @param \EasyInvoice\Models\Invoice $invoice Invoice object
1849 * @param array $payment_data Payment data (method, gateway, transaction_id, amount)
1850 * @return void
1851 */
1852 public function handlePaymentCompleted(int $invoice_id, $invoice, array $payment_data = []): void {
1853 if (!$invoice || !$invoice->getId()) {
1854 return;
1855 }
1856
1857 // Send admin notification
1858 $this->sendAdminPaymentNotification($invoice, $payment_data);
1859
1860 // Send customer confirmation email using proper template system
1861 // Check if payment email is enabled first
1862 if (isset($this->templates['invoice_paid']) && $this->templates['invoice_paid']['enabled']) {
1863 $this->sendInvoiceEmail($invoice, 'paid', array_merge($payment_data, ['skip_bcc' => true]));
1864 }
1865 }
1866
1867 /**
1868 * Get payment method label
1869 *
1870 * @param string $method Payment method
1871 * @return string Payment method label
1872 */
1873 private function getPaymentMethodLabel(string $method): string {
1874 $labels = [
1875 'bank' => __('Bank Transfer', 'easy-invoice'),
1876 'cheque' => __('Cheque', 'easy-invoice'),
1877 'paypal' => __('PayPal', 'easy-invoice'),
1878 'stripe' => __('Stripe', 'easy-invoice'),
1879 'square' => __('Square', 'easy-invoice'),
1880 'mollie' => __('Mollie', 'easy-invoice'),
1881 'authorizenet' => __('Authorize.Net', 'easy-invoice'),
1882 'manual' => __('Manual Payment', 'easy-invoice'),
1883 'online' => __('Online Payment', 'easy-invoice'),
1884 ];
1885
1886 return $labels[$method] ?? ucfirst($method);
1887 }
1888
1889 }