PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.0
2.4.3 2.4.4 2.4.2 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 All 60 releases
easy-invoice / includes / Services / EmailManager.php

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

1,451 lines 55.8 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 // Register AJAX handlers
84 add_action('wp_ajax_easy_invoice_send_invoice_email', [$this, 'handleSendInvoiceEmail']);
85 add_action('wp_ajax_nopriv_easy_invoice_send_invoice_email', [$this, 'handleSendInvoiceEmail']);
86 add_action('wp_ajax_easy_invoice_send_quote_email', [$this, 'handleSendQuoteEmail']);
87 add_action('wp_ajax_nopriv_easy_invoice_send_quote_email', [$this, 'handleSendQuoteEmail']);
88
89 // Add email settings to admin
90 add_action('admin_init', [$this, 'registerEmailSettings']);
91 add_filter('easy_invoice_settings_sections', [$this, 'addEmailSettingsSection']);
92
93 // Refresh settings when they're updated
94 add_action('update_option_easy_invoice_email_from_name', [$this, 'refreshSettings']);
95 add_action('update_option_easy_invoice_email_from_address', [$this, 'refreshSettings']);
96 add_action('update_option_easy_invoice_email_reply_to', [$this, 'refreshSettings']);
97 add_action('update_option_easy_invoice_email_reply_to_name', [$this, 'refreshSettings']);
98 add_action('update_option_easy_invoice_enable_email_styling', [$this, 'refreshSettings']);
99 add_action('update_option_easy_invoice_email_logo', [$this, 'refreshSettings']);
100 add_action('update_option_easy_invoice_email_footer_text', [$this, 'refreshSettings']);
101 add_action('update_option_easy_invoice_bcc_admin', [$this, 'refreshSettings']);
102 add_action('update_option_easy_invoice_admin_email', [$this, 'refreshSettings']);
103 add_action('update_option_easy_invoice_email_subject', [$this, 'refreshSettings']);
104 add_action('update_option_easy_invoice_email_body', [$this, 'refreshSettings']);
105 add_action('update_option_easy_invoice_quote_subject', [$this, 'refreshSettings']);
106 add_action('update_option_easy_invoice_quote_body', [$this, 'refreshSettings']);
107
108 // Add email logs
109 add_action('easy_invoice_email_sent', [$this, 'logEmailSent'], 10, 3);
110 add_action('easy_invoice_email_failed', [$this, 'logEmailFailed'], 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
324 // Add HTML wrapper if enabled
325 if ($this->settings['enable_html'] === 'yes') {
326 $message = $this->wrapInHtmlTemplate($message);
327 }
328
329 // Prepare headers
330 $headers = $this->prepareEmailHeaders();
331
332 // Add BCC to admin if enabled
333 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
334 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
335 }
336
337 return [
338 'to' => $invoice->getCustomerEmail(),
339 'subject' => $subject,
340 'message' => $message,
341 'headers' => $headers
342 ];
343 }
344
345 /**
346 * Prepare quote email data
347 *
348 * @param Quote $quote The quote
349 * @param array $template The email template
350 * @param array $additional_data Additional data
351 * @return array Email data
352 */
353 private function prepareQuoteEmailData(Quote $quote, array $template, array $additional_data = []): array {
354 // Get replacements
355 $replacements = $this->getQuoteReplacements($quote, $additional_data);
356
357 // Process template
358 $subject = $this->processTemplate($template['subject'], $replacements);
359 $message = $this->processTemplate($template['body'], $replacements);
360
361 // Add HTML wrapper if enabled
362 if ($this->settings['enable_html'] === 'yes') {
363 $message = $this->wrapInHtmlTemplate($message);
364 }
365
366 // Prepare headers
367 $headers = $this->prepareEmailHeaders();
368
369 // Add BCC to admin if enabled
370 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
371 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
372 }
373
374 return [
375 'to' => $quote->getCustomerEmail(),
376 'subject' => $subject,
377 'message' => $message,
378 'headers' => $headers
379 ];
380 }
381
382 /**
383 * Prepare payment email data
384 *
385 * @param Invoice $invoice The invoice
386 * @param array $template The email template
387 * @param array $payment_data Payment data
388 * @return array Email data
389 */
390 private function preparePaymentEmailData(Invoice $invoice, array $template, array $payment_data = []): array {
391 // Get replacements
392 $replacements = $this->getPaymentReplacements($invoice, $payment_data);
393
394 // Process template
395 $subject = $this->processTemplate($template['subject'], $replacements);
396 $message = $this->processTemplate($template['body'], $replacements);
397
398 // Add HTML wrapper if enabled
399 if ($this->settings['enable_html'] === 'yes') {
400 $message = $this->wrapInHtmlTemplate($message);
401 }
402
403 // Prepare headers
404 $headers = $this->prepareEmailHeaders();
405
406 // Add BCC to admin if enabled
407 if ($this->settings['bcc_admin'] === 'yes' && !empty($this->settings['admin_email'])) {
408 $headers[] = 'Bcc: ' . $this->settings['admin_email'];
409 }
410
411 return [
412 'to' => $invoice->getCustomerEmail(),
413 'subject' => $subject,
414 'message' => $message,
415 'headers' => $headers
416 ];
417 }
418
419 /**
420 * Get invoice replacements
421 *
422 * @param Invoice $invoice The invoice
423 * @param array $additional_data Additional data
424 * @return array Replacements
425 */
426 private function getInvoiceReplacements(Invoice $invoice, array $additional_data = []): array {
427 $currency_symbol = get_option('easy_invoice_currency_symbol', '$');
428
429 return array_merge([
430 '{{invoice_number}}' => $invoice->getNumber(),
431 '{{invoice_title}}' => $invoice->getTitle(),
432 '{{client_name}}' => $invoice->getCustomerName(),
433 '{{client_email}}' => $invoice->getCustomerEmail(),
434 '{{client_address}}' => $invoice->getCustomerAddress(),
435 '{{company_name}}' => get_bloginfo('name'),
436 '{{company_email}}' => $this->settings['from_email'],
437 '{{company_phone}}' => get_option('easy_invoice_company_phone', ''),
438 '{{company_address}}' => get_option('easy_invoice_company_address', ''),
439 '{{company_website}}' => get_option('easy_invoice_company_website', ''),
440 '{{total_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTotal()),
441 '{{subtotal}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getSubtotal()),
442 '{{tax_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getTaxAmount()),
443 '{{discount_amount}}' => (new \EasyInvoice\Helpers\InvoiceFormatter($invoice))->format($invoice->getDiscountAmount()),
444 '{{due_date}}' => date('F j, Y', strtotime($invoice->getDueDate())),
445 '{{issue_date}}' => date('F j, Y', strtotime($invoice->getIssueDate())),
446 '{{invoice_url}}' => get_permalink($invoice->getId()),
447 '{{payment_url}}' => add_query_arg('payment', '1', get_permalink($invoice->getId())),
448 '{{site_url}}' => get_site_url(),
449 '{{admin_url}}' => admin_url(),
450 '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
451 ], $additional_data);
452 }
453
454 /**
455 * Get quote replacements
456 *
457 * @param Quote $quote The quote
458 * @param array $additional_data Additional data
459 * @return array Replacements
460 */
461 private function getQuoteReplacements(Quote $quote, array $additional_data = []): array {
462 $currency_symbol = get_option('easy_invoice_currency_symbol', '$');
463
464 return array_merge([
465 '{{quote_number}}' => $quote->getNumber(),
466 '{{quote_title}}' => $quote->getTitle(),
467 '{{client_name}}' => $quote->getCustomerName(),
468 '{{client_email}}' => $quote->getCustomerEmail(),
469 '{{client_address}}' => $quote->getCustomerAddress(),
470 '{{company_name}}' => get_bloginfo('name'),
471 '{{company_email}}' => $this->settings['from_email'],
472 '{{company_phone}}' => get_option('easy_invoice_company_phone', ''),
473 '{{company_address}}' => get_option('easy_invoice_company_address', ''),
474 '{{company_website}}' => get_option('easy_invoice_company_website', ''),
475 '{{total_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTotal()),
476 '{{subtotal}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getSubtotal()),
477 '{{tax_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getTaxAmount()),
478 '{{discount_amount}}' => (new \EasyInvoice\Helpers\QuoteFormatter($quote))->format($quote->getDiscountAmount()),
479 '{{expiry_date}}' => date('F j, Y', strtotime($quote->getExpiryDate())),
480 '{{issue_date}}' => date('F j, Y', strtotime($quote->getIssueDate())),
481 '{{quote_url}}' => get_permalink($quote->getId()),
482 '{{site_url}}' => get_site_url(),
483 '{{admin_url}}' => admin_url(),
484 '{{payment_terms}}' => get_option('easy_invoice_payment_terms', __('Due on receipt', 'easy-invoice')),
485 ], $additional_data);
486 }
487
488 /**
489 * Get payment replacements
490 *
491 * @param Invoice $invoice The invoice
492 * @param array $payment_data Payment data
493 * @return array Replacements
494 */
495 private function getPaymentReplacements(Invoice $invoice, array $payment_data = []): array {
496 $replacements = $this->getInvoiceReplacements($invoice, $payment_data);
497
498 // Add payment-specific replacements
499 $replacements['{{payment_amount}}'] = isset($payment_data['amount']) ? $this->formatCurrency($payment_data['amount']) : $this->formatCurrency($invoice->getTotal());
500 $replacements['{{payment_date}}'] = isset($payment_data['date']) ? $payment_data['date'] : current_time('Y-m-d');
501 $replacements['{{payment_method}}'] = isset($payment_data['method']) ? $payment_data['method'] : __('Online Payment', 'easy-invoice');
502 $replacements['{{transaction_id}}'] = isset($payment_data['transaction_id']) ? $payment_data['transaction_id'] : __('N/A', 'easy-invoice');
503 $replacements['{{acceptance_date}}'] = isset($payment_data['acceptance_date']) ? $payment_data['acceptance_date'] : current_time('Y-m-d');
504 $replacements['{{response_date}}'] = isset($payment_data['response_date']) ? $payment_data['response_date'] : current_time('Y-m-d');
505 $replacements['{{decline_reason}}'] = isset($payment_data['decline_reason']) ? $payment_data['decline_reason'] : __('No specific reason provided', 'easy-invoice');
506
507 return $replacements;
508 }
509
510 /**
511 * Process template with replacements
512 *
513 * @param string $template The template
514 * @param array $replacements The replacements
515 * @return string Processed template
516 */
517 private function processTemplate(string $template, array $replacements): string {
518 return easy_invoice_str_replace(array_keys($replacements), array_values($replacements), $template);
519 }
520
521 /**
522 * Prepare email headers
523 *
524 * @return array Headers
525 */
526 private function prepareEmailHeaders(): array {
527 $headers = [
528 'Content-Type: text/html; charset=UTF-8',
529 'From: ' . $this->settings['from_name'] . ' <' . $this->settings['from_email'] . '>',
530 ];
531
532 // Add Reply-To if set
533 if (!empty($this->settings['reply_to_email'])) {
534 $reply_to_name = !empty($this->settings['reply_to_name']) ? $this->settings['reply_to_name'] : $this->settings['from_name'];
535 $headers[] = 'Reply-To: ' . $reply_to_name . ' <' . $this->settings['reply_to_email'] . '>';
536 }
537
538 return $headers;
539 }
540
541 /**
542 * Wrap message in HTML template
543 *
544 * @param string $message The message
545 * @return string HTML wrapped message
546 */
547 private function wrapInHtmlTemplate(string $message): string {
548 $logo_html = '';
549 if (!empty($this->settings['email_logo'])) {
550 $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>';
551 }
552
553 $footer_html = '';
554 if (!empty($this->settings['footer_text'])) {
555 $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>';
556 }
557
558 return '
559 <!DOCTYPE html>
560 <html>
561 <head>
562 <meta charset="UTF-8">
563 <meta name="viewport" content="width=device-width, initial-scale=1.0">
564 <title>' . esc_html($this->settings['from_name']) . '</title>
565 <style>
566 body {
567 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
568 line-height: 1.6;
569 color: #374151;
570 margin: 0;
571 padding: 0;
572 background-color: #f9fafb;
573 }
574 .email-container {
575 max-width: 600px;
576 margin: 0 auto;
577 background-color: #ffffff;
578 border-radius: 12px;
579 box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
580 overflow: hidden;
581 }
582 .email-header {
583 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
584 padding: 50px 30px;
585 text-align: center;
586 position: relative;
587 }
588 .email-header::before {
589 content: "";
590 position: absolute;
591 top: 0;
592 left: 0;
593 right: 0;
594 bottom: 0;
595 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");
596 opacity: 0.3;
597 }
598 .email-header h1 {
599 color: #ffffff;
600 margin: 0;
601 font-size: 28px;
602 font-weight: 700;
603 position: relative;
604 z-index: 1;
605 }
606 .email-content {
607 padding: 50px 40px;
608 background: #ffffff;
609 }
610 .email-content p {
611 margin: 0 0 20px 0;
612 color: #374151;
613 line-height: 1.7;
614 }
615 .email-content h2 {
616 color: #1f2937;
617 font-size: 28px;
618 font-weight: 700;
619 margin: 0 0 30px 0;
620 text-align: center;
621 }
622 .email-content h3 {
623 color: #374151;
624 font-size: 20px;
625 font-weight: 600;
626 margin: 0 0 16px 0;
627 }
628 .email-footer {
629 background-color: #f9fafb;
630 padding: 40px 30px;
631 text-align: center;
632 border-top: 1px solid #e5e7eb;
633 }
634 .email-footer p {
635 margin: 0;
636 color: #6b7280;
637 font-size: 14px;
638 }
639 .highlight-box {
640 background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
641 border-left: 4px solid #3b82f6;
642 padding: 30px;
643 margin: 30px 0;
644 border-radius: 0 12px 12px 0;
645 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
646 }
647 .highlight-box p {
648 margin: 0;
649 font-size: 16px;
650 line-height: 1.6;
651 }
652 .highlight-box strong {
653 color: #1f2937;
654 font-weight: 600;
655 }
656 .info-box {
657 background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
658 border: 1px solid #93c5fd;
659 border-radius: 12px;
660 padding: 25px;
661 margin: 30px 0;
662 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
663 }
664 .info-box p {
665 margin: 0;
666 color: #1e40af;
667 font-size: 15px;
668 line-height: 1.7;
669 }
670 .info-box strong {
671 color: #1e3a8a;
672 font-weight: 600;
673 }
674 .success-box {
675 background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
676 border: 1px solid #6ee7b7;
677 border-radius: 12px;
678 padding: 25px;
679 margin: 30px 0;
680 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
681 }
682 .success-box p {
683 margin: 0;
684 color: #065f46;
685 font-size: 15px;
686 line-height: 1.7;
687 }
688 .success-box strong {
689 color: #047857;
690 font-weight: 600;
691 }
692 .warning-box {
693 background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
694 border: 1px solid #f59e0b;
695 border-radius: 12px;
696 padding: 25px;
697 margin: 30px 0;
698 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
699 }
700 .warning-box p {
701 margin: 0;
702 color: #92400e;
703 font-size: 15px;
704 line-height: 1.7;
705 }
706 .warning-box strong {
707 color: #78350f;
708 font-weight: 600;
709 }
710 .button {
711 display: inline-block;
712 background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
713 color: #ffffff;
714 padding: 14px 28px;
715 text-decoration: none;
716 border-radius: 8px;
717 font-weight: 600;
718 margin: 20px 0;
719 box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3);
720 transition: all 0.2s ease;
721 }
722 .button:hover {
723 background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
724 transform: translateY(-1px);
725 box-shadow: 0 6px 8px -1px rgba(59, 130, 246, 0.4);
726 }
727 .divider {
728 height: 1px;
729 background: linear-gradient(90deg, transparent 0%, #e5e7eb 50%, transparent 100%);
730 margin: 40px 0;
731 }
732 .amount-highlight {
733 font-size: 28px;
734 font-weight: 700;
735 color: #059669;
736 text-align: center;
737 margin: 25px 0;
738 display: block;
739 }
740 .status-badge {
741 display: inline-block;
742 padding: 6px 12px;
743 border-radius: 20px;
744 font-size: 12px;
745 font-weight: 600;
746 text-transform: uppercase;
747 letter-spacing: 0.5px;
748 }
749 .status-paid {
750 background: #d1fae5;
751 color: #065f46;
752 }
753 .status-pending {
754 background: #fef3c7;
755 color: #92400e;
756 }
757 .status-overdue {
758 background: #fee2e2;
759 color: #991b1b;
760 }
761 @media only screen and (max-width: 600px) {
762 .email-content { padding: 25px 20px; }
763 .email-header { padding: 35px 20px; }
764 .email-header h1 { font-size: 24px; }
765 .email-content h2 { font-size: 22px; }
766 .highlight-box, .info-box, .success-box, .warning-box { padding: 20px; }
767 .amount-highlight { font-size: 24px; }
768 }
769 </style>
770 </head>
771 <body>
772 <div class="email-container">
773 ' . $logo_html . '
774 <div class="email-content">
775 ' . wpautop($message) . '
776 </div>
777 ' . $footer_html . '
778 </div>
779 </body>
780 </html>';
781 }
782
783 /**
784 * Handle AJAX send invoice email
785 */
786 public function handleSendInvoiceEmail(): void {
787 // Verify nonce
788 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_send_invoice_email')) {
789 wp_send_json_error(__('Security check failed', 'easy-invoice'));
790 }
791
792 // Get invoice ID
793 $invoice_id = isset($_POST['invoice_id']) ? intval($_POST['invoice_id']) : 0;
794 if (!$invoice_id) {
795 wp_send_json_error(__('Invalid invoice ID', 'easy-invoice'));
796 }
797
798 // Get invoice
799 $repository = \EasyInvoice\Providers\InvoiceServiceProvider::getInvoiceRepository();
800 $invoice = $repository->find($invoice_id);
801
802 if (!$invoice) {
803 wp_send_json_error(__('Invoice not found', 'easy-invoice'));
804 }
805
806 // Send email
807 $result = $this->sendInvoiceEmail($invoice, 'new');
808
809 if ($result['success']) {
810 wp_send_json_success($result['message']);
811 } else {
812 wp_send_json_error($result['message']);
813 }
814 }
815
816 /**
817 * Handle AJAX send quote email
818 */
819 public function handleSendQuoteEmail(): void {
820 // Verify nonce
821 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'easy_invoice_send_quote_email')) {
822 wp_send_json_error(__('Security check failed', 'easy-invoice'));
823 }
824
825 // Get quote ID
826 $quote_id = isset($_POST['quote_id']) ? intval($_POST['quote_id']) : 0;
827 if (!$quote_id) {
828 wp_send_json_error(__('Invalid quote ID', 'easy-invoice'));
829 }
830
831 // Get quote
832 $repository = \EasyInvoice\Providers\QuoteServiceProvider::getQuoteRepository();
833 $quote = $repository->find($quote_id);
834
835 if (!$quote) {
836 wp_send_json_error(__('Quote not found', 'easy-invoice'));
837 }
838
839 // Send email
840 $result = $this->sendQuoteEmail($quote, 'new');
841
842 if ($result['success']) {
843 // Log the quote email sent
844 $quote_log_service = new \EasyInvoice\Services\QuoteLogService();
845 $quote_log_service->logSent($quote_id, $quote->getCustomerEmail());
846
847 wp_send_json_success($result['message']);
848 } else {
849 wp_send_json_error($result['message']);
850 }
851 }
852
853 /**
854 * Register email settings
855 */
856 public function registerEmailSettings(): void {
857 // Email settings section
858 add_settings_section(
859 'easy_invoice_email_settings',
860 __('Email Configuration', 'easy-invoice'),
861 [$this, 'emailSettingsSectionCallback'],
862 'easy_invoice_settings'
863 );
864
865 // Register settings
866 register_setting('easy_invoice_settings', 'easy_invoice_email_from_name');
867 register_setting('easy_invoice_settings', 'easy_invoice_email_from_address');
868 register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to');
869 register_setting('easy_invoice_settings', 'easy_invoice_email_reply_to_name');
870 register_setting('easy_invoice_settings', 'easy_invoice_enable_email_styling');
871 register_setting('easy_invoice_settings', 'easy_invoice_email_logo');
872 register_setting('easy_invoice_settings', 'easy_invoice_email_footer_text');
873 register_setting('easy_invoice_settings', 'easy_invoice_bcc_admin');
874 register_setting('easy_invoice_settings', 'easy_invoice_admin_email');
875
876 // Add settings fields
877 add_settings_field(
878 'easy_invoice_email_from_name',
879 __('From Name', 'easy-invoice'),
880 [$this, 'textFieldCallback'],
881 'easy_invoice_settings',
882 'easy_invoice_email_settings',
883 ['label_for' => 'easy_invoice_email_from_name']
884 );
885
886 add_settings_field(
887 'easy_invoice_email_from_address',
888 __('From Email Address', 'easy-invoice'),
889 [$this, 'emailFieldCallback'],
890 'easy_invoice_settings',
891 'easy_invoice_email_settings',
892 ['label_for' => 'easy_invoice_email_from_address']
893 );
894
895 add_settings_field(
896 'easy_invoice_email_reply_to',
897 __('Reply-To Email', 'easy-invoice'),
898 [$this, 'emailFieldCallback'],
899 'easy_invoice_settings',
900 'easy_invoice_email_settings',
901 ['label_for' => 'easy_invoice_email_reply_to']
902 );
903
904 add_settings_field(
905 'easy_invoice_enable_email_styling',
906 __('Enable HTML Emails', 'easy-invoice'),
907 [$this, 'checkboxFieldCallback'],
908 'easy_invoice_settings',
909 'easy_invoice_email_settings',
910 ['label_for' => 'easy_invoice_enable_email_styling']
911 );
912
913 add_settings_field(
914 'easy_invoice_bcc_admin',
915 __('BCC Admin on All Emails', 'easy-invoice'),
916 [$this, 'checkboxFieldCallback'],
917 'easy_invoice_settings',
918 'easy_invoice_email_settings',
919 ['label_for' => 'easy_invoice_bcc_admin']
920 );
921 }
922
923 /**
924 * Add email settings section
925 *
926 * @param array $sections Settings sections
927 * @return array Modified sections
928 */
929 public function addEmailSettingsSection(array $sections): array {
930 $sections['email'] = [
931 'title' => __('Email Settings', 'easy-invoice'),
932 'description' => __('Configure email sending options and templates', 'easy-invoice'),
933 'icon' => 'fas fa-envelope',
934 'fields' => [
935 'easy_invoice_email_from_name' => [
936 'label' => __('From Name', 'easy-invoice'),
937 'type' => 'text',
938 'default' => get_bloginfo('name'),
939 'col_span' => 'sm:col-span-3'
940 ],
941 'easy_invoice_email_from_address' => [
942 'label' => __('From Email Address', 'easy-invoice'),
943 'type' => 'email',
944 'default' => get_bloginfo('admin_email'),
945 'col_span' => 'sm:col-span-3'
946 ],
947 'easy_invoice_email_reply_to' => [
948 'label' => __('Reply-To Email', 'easy-invoice'),
949 'type' => 'email',
950 'default' => '',
951 'col_span' => 'sm:col-span-3'
952 ],
953 'easy_invoice_enable_email_styling' => [
954 'label' => __('Enable HTML Emails', 'easy-invoice'),
955 'type' => 'checkbox',
956 'default' => 'yes',
957 'col_span' => 'sm:col-span-3'
958 ],
959 'easy_invoice_bcc_admin' => [
960 'label' => __('BCC Admin on All Emails', 'easy-invoice'),
961 'type' => 'checkbox',
962 'default' => 'no',
963 'col_span' => 'sm:col-span-3'
964 ],
965 'easy_invoice_email_logo' => [
966 'label' => __('Email Logo URL', 'easy-invoice'),
967 'type' => 'url',
968 'default' => '',
969 'col_span' => 'sm:col-span-6'
970 ],
971 'easy_invoice_email_footer_text' => [
972 'label' => __('Email Footer Text', 'easy-invoice'),
973 'type' => 'textarea',
974 'default' => '',
975 'col_span' => 'sm:col-span-6'
976 ],
977 ]
978 ];
979
980 return $sections;
981 }
982
983 /**
984 * Email settings section callback
985 */
986 public function emailSettingsSectionCallback(): void {
987 echo '<p>' . __('Configure how emails are sent from Easy Invoice.', 'easy-invoice') . '</p>';
988 }
989
990 /**
991 * Text field callback
992 *
993 * @param array $args Field arguments
994 */
995 public function textFieldCallback(array $args): void {
996 $field_id = $args['label_for'];
997 $value = get_option($field_id, '');
998 echo '<input type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($value) . '" class="regular-text">';
999 }
1000
1001 /**
1002 * Email field callback
1003 *
1004 * @param array $args Field arguments
1005 */
1006 public function emailFieldCallback(array $args): void {
1007 $field_id = $args['label_for'];
1008 $value = get_option($field_id, '');
1009 echo '<input type="email" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="' . esc_attr($value) . '" class="regular-text">';
1010 }
1011
1012 /**
1013 * Checkbox field callback
1014 *
1015 * @param array $args Field arguments
1016 */
1017 public function checkboxFieldCallback(array $args): void {
1018 $field_id = $args['label_for'];
1019 $value = get_option($field_id, '');
1020 echo '<input type="checkbox" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_id) . '" value="yes"' . checked($value, 'yes', false) . '>';
1021 echo '<span class="description">' . __('Enable this option', 'easy-invoice') . '</span>';
1022 }
1023
1024 /**
1025 * Log email sent
1026 *
1027 * @param Invoice $invoice The invoice
1028 * @param string $email The email address
1029 * @param string $type The email type
1030 */
1031 public function logEmailSent($invoice, string $email, string $type): void {
1032 $this->log(sprintf('Email sent to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'info');
1033 }
1034
1035 /**
1036 * Log email failed
1037 *
1038 * @param Invoice $invoice The invoice
1039 * @param string $email The email address
1040 * @param string $type The email type
1041 */
1042 public function logEmailFailed($invoice, string $email, string $type): void {
1043 $this->log(sprintf('Email failed to %s for invoice #%s (%s)', $email, $invoice->getNumber(), $type), 'error');
1044 }
1045
1046 /**
1047 * Get default invoice template
1048 *
1049 * @return string Template
1050 */
1051 private function getDefaultInvoiceTemplate(): string {
1052 return '<h2>📄 Your Invoice is Ready</h2>
1053
1054 <p>Dear {{client_name}},</p>
1055
1056 <div class="highlight-box">
1057 <p><strong>Invoice #{{invoice_number}}</strong><br>
1058 <span class="amount-highlight">{{total_amount}}</span><br>
1059 Due Date: <strong>{{due_date}}</strong></p>
1060 </div>
1061
1062 <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>
1063
1064 <div class="info-box">
1065 <p><strong>📋 Payment Details:</strong><br>
1066 • Invoice Number: {{invoice_number}}<br>
1067 • Total Amount: {{total_amount}}<br>
1068 • Due Date: {{due_date}}<br>
1069 • Payment Terms: {{payment_terms}}</p>
1070 </div>
1071
1072 <div class="highlight-box">
1073 <p><strong>🔗 View Invoice Online:</strong><br>
1074 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1075 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1076 </div>
1077
1078 <div class="warning-box">
1079 <p><strong>⚠️ Important:</strong> Please ensure payment is received by the due date to avoid any late fees or service interruptions.</p>
1080 </div>
1081
1082 <p>If you have any questions about this invoice, please do not hesitate to contact us.</p>
1083
1084 <div class="divider"></div>
1085
1086 <p>Thank you for your business!</p>
1087
1088 <p>Best regards,<br>
1089 <strong>{{company_name}}</strong><br>
1090 {{company_email}}</p>';
1091 }
1092
1093 private function getDefaultReminderTemplate(): string {
1094 return '<h2>⏰ Payment Reminder</h2>
1095
1096 <p>Dear {{client_name}},</p>
1097
1098 <div class="warning-box">
1099 <p><strong>Invoice #{{invoice_number}}</strong><br>
1100 <span class="amount-highlight">{{total_amount}}</span><br>
1101 Due Date: <strong>{{due_date}}</strong></p>
1102 </div>
1103
1104 <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>
1105
1106 <div class="info-box">
1107 <p><strong>💳 Payment Options:</strong><br>
1108 • Online payment through our secure portal<br>
1109 • Bank transfer to the details provided<br>
1110 • Check or money order</p>
1111 </div>
1112
1113 <div class="highlight-box">
1114 <p><strong>🔗 View Invoice Online:</strong><br>
1115 <a href="{{invoice_url}}" style="color: #3b82f6; text-decoration: underline;">{{invoice_url}}</a></p>
1116 <p><strong>Shortcode:</strong> <code>[easy_invoice_url number="{{invoice_number}}" text="View Invoice"]</code></p>
1117 </div>
1118
1119 <div class="highlight-box">
1120 <p><strong>📞 Need Help?</strong> If you have any questions or need to discuss payment arrangements, please contact us immediately.</p>
1121 </div>
1122
1123 <p>Thank you for your prompt attention to this matter.</p>
1124
1125 <div class="divider"></div>
1126
1127 <p>Best regards,<br>
1128 <strong>{{company_name}}</strong><br>
1129 {{company_email}}</p>';
1130 }
1131
1132 private function getDefaultPaymentTemplate(): string {
1133 return '<h2>�
1134 Payment Received - Thank You!</h2>
1135
1136 <p>Dear {{client_name}},</p>
1137
1138 <div class="success-box">
1139 <p><strong>Payment Confirmation</strong><br>
1140 Invoice #{{invoice_number}}<br>
1141 <span class="amount-highlight">{{payment_amount}}</span><br>
1142 Payment Date: <strong>{{payment_date}}</strong><br>
1143 Payment Method: <strong>{{payment_method}}</strong></p>
1144 </div>
1145
1146 <p>We have successfully received your payment. Thank you for your prompt payment!</p>
1147
1148 <div class="info-box">
1149 <p><strong>📊 Payment Details:</strong><br>
1150 • Invoice Number: {{invoice_number}}<br>
1151 • Amount Paid: {{payment_amount}}<br>
1152 • Payment Date: {{payment_date}}<br>
1153 • Payment Method: {{payment_method}}<br>
1154 • Transaction ID: {{transaction_id}}</p>
1155 </div>
1156
1157 <div class="highlight-box">
1158 <p><strong>🎉 Status: PAID</strong><br>
1159 Your payment has been processed and your account is now up to date. We appreciate your business!</p>
1160 </div>
1161
1162 <p>If you have any questions about this payment or need a receipt, please don\'t hesitate to contact us.</p>
1163
1164 <div class="divider"></div>
1165
1166 <p>Thank you for choosing our services!</p>
1167
1168 <p>Best regards,<br>
1169 <strong>{{company_name}}</strong><br>
1170 {{company_email}}</p>';
1171 }
1172
1173 private function getDefaultQuoteTemplate(): string {
1174 return '<h2>📋 Your Quote is Ready</h2>
1175
1176 <p>Dear {{client_name}},</p>
1177
1178 <div class="highlight-box">
1179 <p><strong>Quote #{{quote_number}}</strong><br>
1180 <span class="amount-highlight">{{total_amount}}</span><br>
1181 Valid Until: <strong>{{expiry_date}}</strong></p>
1182 </div>
1183
1184 <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>
1185
1186 <div class="info-box">
1187 <p><strong>📋 Quote Summary:</strong><br>
1188 • Quote Number: {{quote_number}}<br>
1189 • Total Amount: {{total_amount}}<br>
1190 • Valid Until: {{expiry_date}}<br>
1191 • Terms: {{payment_terms}}</p>
1192 </div>
1193
1194 <div class="highlight-box">
1195 <p><strong>🔗 View Quote Online:</strong><br>
1196 <a href="{{quote_url}}" style="color: #3b82f6; text-decoration: underline;">{{quote_url}}</a></p>
1197 <p><strong>Shortcode:</strong> <code>[easy_quote_url number="{{quote_number}}" text="View Quote"]</code></p>
1198 </div>
1199
1200 <div class="warning-box">
1201 <p><strong>⏰ Time Sensitive:</strong> This quote is valid until {{expiry_date}}. Please review and respond within this timeframe.</p>
1202 </div>
1203
1204 <p>If you have any questions or would like to discuss any aspects of this quote, please contact us.</p>
1205
1206 <div class="divider"></div>
1207
1208 <p>We look forward to working with you!</p>
1209
1210 <p>Best regards,<br>
1211 <strong>{{company_name}}</strong><br>
1212 {{company_email}}</p>';
1213 }
1214
1215 private function getDefaultQuoteAcceptedTemplate(): string {
1216 return '<h2>🎉 Quote Accepted - Project Confirmed!</h2>
1217
1218 <p>Dear {{client_name}},</p>
1219
1220 <div class="success-box">
1221 <p><strong>Quote #{{quote_number}} - ACCEPTED</strong><br>
1222 <span class="amount-highlight">{{total_amount}}</span><br>
1223 Acceptance Date: <strong>{{acceptance_date}}</strong></p>
1224 </div>
1225
1226 <p>Thank you for accepting our quote! We\'re excited to begin working on your project.</p>
1227
1228 <div class="info-box">
1229 <p><strong>🚀 Next Steps:</strong><br>
1230 • We will create an invoice for the accepted quote<br>
1231 • You will receive payment instructions<br>
1232 • Project work will begin as scheduled</p>
1233 </div>
1234
1235 <div class="highlight-box">
1236 <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>
1237 </div>
1238
1239 <div class="divider"></div>
1240
1241 <p>Thank you for choosing our services!</p>
1242
1243 <p>Best regards,<br>
1244 <strong>{{company_name}}</strong><br>
1245 {{company_email}}</p>';
1246 }
1247
1248 private function getDefaultQuoteDeclinedTemplate(): string {
1249 return '<h2>📝 Quote Response Received</h2>
1250
1251 <p>Dear {{client_name}},</p>
1252
1253 <div class="warning-box">
1254 <p><strong>Quote #{{quote_number}} - DECLINED</strong><br>
1255 Response Date: <strong>{{response_date}}</strong></p>
1256 </div>
1257
1258 <p>We have received your response regarding our quote. We understand that this quote may not have met your current needs.</p>
1259
1260 <div class="info-box">
1261 <p><strong>📋 Feedback:</strong><br>
1262 • Reason: {{decline_reason}}<br>
1263 • Response Date: {{response_date}}</p>
1264 </div>
1265
1266 <div class="highlight-box">
1267 <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>
1268 </div>
1269
1270 <div class="divider"></div>
1271
1272 <p>Thank you for considering our services!</p>
1273
1274 <p>Best regards,<br>
1275 <strong>{{company_name}}</strong><br>
1276 {{company_email}}</p>';
1277 }
1278
1279 /**
1280 * Refresh settings and templates
1281 * Call this method when settings are updated
1282 */
1283 public function refreshSettings(): void {
1284 $this->loadSettings();
1285 $this->loadTemplates();
1286 }
1287
1288 /**
1289 * Get email templates
1290 *
1291 * @return array Templates
1292 */
1293 public function getTemplates(): array {
1294 return $this->templates;
1295 }
1296
1297 /**
1298 * Get email settings
1299 *
1300 * @return array Settings
1301 */
1302 public function getSettings(): array {
1303 return $this->settings;
1304 }
1305
1306 /**
1307 * Test email functionality
1308 *
1309 * @param string $to_email Email to send test to
1310 * @return array Result
1311 */
1312 public function testEmail(string $to_email): array {
1313 $subject = 'Easy Invoice - Email Configuration Test';
1314 $message = '<h2>🧪 Email Configuration Test</h2>
1315
1316 <p>Hello!</p>
1317
1318 <div class="success-box">
1319 <p><strong>�
1320 Test Email Successfully Sent</strong><br>
1321 Date: <strong>' . current_time('Y-m-d H:i:s') . '</strong><br>
1322 To: <strong>' . esc_html($to_email) . '</strong></p>
1323 </div>
1324
1325 <p>This is a test email to verify that your Easy Invoice email configuration is working correctly.</p>
1326
1327 <div class="info-box">
1328 <p><strong>⚙️ Email Settings Verified:</strong><br>
1329 • From Name: ' . esc_html($this->settings['from_name']) . '<br>
1330 • From Email: ' . esc_html($this->settings['from_email']) . '<br>
1331 • Reply-To: ' . esc_html($this->settings['reply_to_email'] ?: 'Not set') . '<br>
1332 • HTML Emails: ' . ($this->settings['enable_html'] === 'yes' ? 'Enabled' : 'Disabled') . '</p>
1333 </div>
1334
1335 <div class="highlight-box">
1336 <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>
1337 </div>
1338
1339 <div class="divider"></div>
1340
1341 <p>Thank you for using Easy Invoice!</p>
1342
1343 <p>Best regards,<br>
1344 <strong>' . esc_html($this->settings['from_name']) . '</strong></p>';
1345
1346 if ($this->settings['enable_html'] === 'yes') {
1347 $message = $this->wrapInHtmlTemplate($message);
1348 }
1349
1350 $headers = $this->prepareEmailHeaders();
1351
1352 $sent = $this->sendEmail($to_email, $subject, $message, $headers);
1353
1354 if ($sent) {
1355 return ['success' => true, 'message' => __('Test email sent successfully', 'easy-invoice')];
1356 } else {
1357 return ['success' => false, 'message' => __('Failed to send test email', 'easy-invoice')];
1358 }
1359 }
1360
1361 /**
1362 * Send test template email with custom subject and body
1363 *
1364 * @param string $to_email Email to send test to
1365 * @param string $subject Email subject
1366 * @param string $body Email body
1367 * @return array Result
1368 */
1369 public function sendTestTemplateEmail(string $to_email, string $subject, string $body): array {
1370 if ($this->settings['enable_html'] === 'yes') {
1371 $body = $this->wrapInHtmlTemplate($body);
1372 }
1373
1374 $headers = $this->prepareEmailHeaders();
1375
1376 $sent = $this->sendEmail($to_email, $subject, $body, $headers);
1377
1378 if ($sent) {
1379 return ['success' => true, 'message' => __('Template test email sent successfully', 'easy-invoice')];
1380 } else {
1381 return ['success' => false, 'message' => __('Failed to send template test email', 'easy-invoice')];
1382 }
1383 }
1384
1385 /**
1386 * Send payment received email
1387 *
1388 * @param Invoice $invoice The invoice
1389 * @param array $payment_data Payment data
1390 * @return array Result array with success status and message
1391 */
1392 public function sendPaymentEmail(Invoice $invoice, array $payment_data = []): array {
1393 try {
1394 // Validate invoice
1395 if (!$invoice || !$invoice->getId()) {
1396 return ['success' => false, 'message' => __('Invalid invoice', 'easy-invoice')];
1397 }
1398
1399 // Get client email
1400 $client_email = $invoice->getCustomerEmail();
1401 if (empty($client_email)) {
1402 return ['success' => false, 'message' => __('Client email is missing', 'easy-invoice')];
1403 }
1404
1405 // Get template
1406 $template_key = 'invoice_paid';
1407 if (!isset($this->templates[$template_key])) {
1408 return ['success' => false, 'message' => __('Payment email template not found', 'easy-invoice')];
1409 }
1410
1411 $template = $this->templates[$template_key];
1412
1413 // Check if email is enabled
1414 if (!$template['enabled']) {
1415 return ['success' => false, 'message' => __('Payment received email is disabled', 'easy-invoice')];
1416 }
1417
1418 // Prepare email data
1419 $email_data = $this->preparePaymentEmailData($invoice, $template, $payment_data);
1420
1421 // Send email
1422 $sent = $this->sendEmail(
1423 $email_data['to'],
1424 $email_data['subject'],
1425 $email_data['message'],
1426 $email_data['headers']
1427 );
1428
1429 if ($sent) {
1430 // Log success
1431 do_action('easy_invoice_payment_email_sent', $invoice, $client_email, $payment_data);
1432
1433 return [
1434 'success' => true,
1435 'message' => __('Payment email sent successfully', 'easy-invoice'),
1436 'email_data' => $email_data
1437 ];
1438 } else {
1439 // Log failure
1440 do_action('easy_invoice_payment_email_failed', $invoice, $client_email, $payment_data);
1441
1442 return ['success' => false, 'message' => __('Failed to send payment email', 'easy-invoice')];
1443 }
1444
1445 } catch (\Exception $e) {
1446 $this->log('Payment email sending error: ' . $e->getMessage(), 'error');
1447 return ['success' => false, 'message' => __('Error sending payment email: ', 'easy-invoice') . $e->getMessage()];
1448 }
1449 }
1450
1451 }