PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.1.2
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.1.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 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.1.2, at includes/Services/EmailManager.php

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