Files
WooNooW/includes/Core/Notifications/EmailRenderer.php
dwindown af2a3d3dd5 fix: Enable email notifications by default with default templates
🐛 CRITICAL FIX - Root Cause Found:

Problem 1: Events Not Enabled by Default
- is_event_enabled() returned false if not configured
- Required explicit admin configuration
- Plugin didn't work out-of-the-box

Solution:
- Enable email notifications by default if not configured
- Allow plugin to work with default templates
- Users can still disable via admin if needed

Problem 2: Default Templates Not Loading
- EmailRenderer called get_template() with only 2 args
- Missing $recipient_type parameter
- Default template lookup failed

Solution:
- Pass recipient_type to get_template()
- Proper default template lookup
- Added debug logging for template resolution

📝 Changes:

1. EmailManager::is_event_enabled()
   - Returns true by default for email channel
   - Logs when using default (not configured)
   - Respects explicit disable if configured

2. EmailRenderer::get_template_settings()
   - Pass recipient_type to TemplateProvider
   - Log template found/not found
   - Proper default template resolution

🎯 Result:
- Plugin works out-of-the-box
- Default templates used if not customized
- Email notifications sent without configuration
- Users can still customize in admin

 Expected Behavior:
1. Install plugin
2. Create order
3. Email sent automatically (default template)
4. Customize templates in admin (optional)

This fixes the issue where check-settings.php showed:
- Email: ✗ NOT CONFIGURED
- Templates: 0

Now it will use defaults and send emails!
2025-11-18 18:25:27 +07:00

483 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Email Renderer
*
* Renders email templates with content
*
* @package WooNooW\Core\Notifications
*/
namespace WooNooW\Core\Notifications;
class EmailRenderer {
/**
* Instance
*/
private static $instance = null;
/**
* Get instance
*/
public static function instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Render email
*
* @param string $event_id Event ID (order_placed, order_processing, etc.)
* @param string $recipient_type Recipient type (staff, customer)
* @param mixed $data Order, Product, or Customer object
* @param array $extra_data Additional data
* @return array|null ['to', 'subject', 'body']
*/
public function render($event_id, $recipient_type, $data, $extra_data = []) {
// Get template settings
$template_settings = $this->get_template_settings($event_id, $recipient_type);
if (!$template_settings) {
return null;
}
// Get recipient email
$to = $this->get_recipient_email($recipient_type, $data);
if (!$to) {
return null;
}
// Get variables
$variables = $this->get_variables($event_id, $data, $extra_data);
// Replace variables in subject and content
$subject = $this->replace_variables($template_settings['subject'], $variables);
$content = $this->replace_variables($template_settings['body'], $variables);
// Parse cards in content
$content = $this->parse_cards($content);
// Get HTML template
$template_path = $this->get_design_template();
// Render final HTML
$html = $this->render_html($template_path, $content, $subject, $variables);
return [
'to' => $to,
'subject' => $subject,
'body' => $html,
];
}
/**
* Get template settings
*
* @param string $event_id
* @param string $recipient_type
* @return array|null
*/
private function get_template_settings($event_id, $recipient_type) {
// Get saved template (with recipient_type for proper default template lookup)
$template = TemplateProvider::get_template($event_id, 'email', $recipient_type);
if (!$template) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[EmailRenderer] No template found for event: ' . $event_id . ', recipient: ' . $recipient_type);
}
return null;
}
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[EmailRenderer] Template found - Subject: ' . ($template['subject'] ?? 'no subject'));
}
// Get design template preference
$settings = get_option('woonoow_notification_settings', []);
$design = $settings['email_design_template'] ?? 'modern';
return [
'subject' => $template['subject'] ?? '',
'body' => $template['body'] ?? '',
'design' => $design,
];
}
/**
* Get recipient email
*
* @param string $recipient_type
* @param mixed $data
* @return string|null
*/
private function get_recipient_email($recipient_type, $data) {
if ($recipient_type === 'staff') {
return get_option('admin_email');
}
// Customer
if ($data instanceof \WC_Order) {
return $data->get_billing_email();
}
if ($data instanceof \WC_Customer) {
return $data->get_email();
}
return null;
}
/**
* Get variables for template
*
* @param string $event_id
* @param mixed $data
* @param array $extra_data
* @return array
*/
private function get_variables($event_id, $data, $extra_data = []) {
$variables = [
'store_name' => get_bloginfo('name'),
'store_url' => home_url(),
'site_title' => get_bloginfo('name'),
];
// Order variables
if ($data instanceof \WC_Order) {
$variables = array_merge($variables, [
'order_number' => $data->get_order_number(),
'order_id' => $data->get_id(),
'order_date' => $data->get_date_created()->date('F j, Y'),
'order_total' => $data->get_formatted_order_total(),
'order_subtotal' => wc_price($data->get_subtotal()),
'order_tax' => wc_price($data->get_total_tax()),
'order_shipping' => wc_price($data->get_shipping_total()),
'order_discount' => wc_price($data->get_discount_total()),
'order_status' => wc_get_order_status_name($data->get_status()),
'order_url' => $data->get_view_order_url(),
'payment_method' => $data->get_payment_method_title(),
'shipping_method' => $data->get_shipping_method(),
'customer_name' => $data->get_formatted_billing_full_name(),
'customer_first_name' => $data->get_billing_first_name(),
'customer_last_name' => $data->get_billing_last_name(),
'customer_email' => $data->get_billing_email(),
'customer_phone' => $data->get_billing_phone(),
'billing_address' => $data->get_formatted_billing_address(),
'shipping_address' => $data->get_formatted_shipping_address(),
]);
// Order items
$items_html = '';
foreach ($data->get_items() as $item) {
$product = $item->get_product();
$items_html .= sprintf(
'<tr><td>%s × %d</td><td>%s</td></tr>',
$item->get_name(),
$item->get_quantity(),
wc_price($item->get_total())
);
}
$variables['order_items'] = $items_html;
}
// Product variables
if ($data instanceof \WC_Product) {
$variables = array_merge($variables, [
'product_id' => $data->get_id(),
'product_name' => $data->get_name(),
'product_sku' => $data->get_sku(),
'product_price' => wc_price($data->get_price()),
'product_url' => get_permalink($data->get_id()),
'stock_quantity' => $data->get_stock_quantity(),
'stock_status' => $data->get_stock_status(),
]);
}
// Customer variables
if ($data instanceof \WC_Customer) {
$variables = array_merge($variables, [
'customer_id' => $data->get_id(),
'customer_name' => $data->get_display_name(),
'customer_first_name' => $data->get_first_name(),
'customer_last_name' => $data->get_last_name(),
'customer_email' => $data->get_email(),
'customer_username' => $data->get_username(),
]);
}
// Merge extra data
$variables = array_merge($variables, $extra_data);
return apply_filters('woonoow_email_variables', $variables, $event_id, $data);
}
/**
* Parse [card] tags and convert to HTML
*
* @param string $content
* @return string
*/
private function parse_cards($content) {
// Match [card ...] ... [/card] patterns
preg_match_all('/\[card([^\]]*)\](.*?)\[\/card\]/s', $content, $matches, PREG_SET_ORDER);
if (empty($matches)) {
// No cards found, wrap entire content in a single card
return $this->render_card($content, []);
}
$html = '';
foreach ($matches as $match) {
$attributes = $this->parse_card_attributes($match[1]);
$card_content = $match[2];
$html .= $this->render_card($card_content, $attributes);
$html .= $this->render_card_spacing();
}
// Remove last spacing
$html = preg_replace('/<table[^>]*class="card-spacing"[^>]*>.*?<\/table>\s*$/s', '', $html);
return $html;
}
/**
* Parse card attributes from [card ...] tag
*
* @param string $attr_string
* @return array
*/
private function parse_card_attributes($attr_string) {
$attributes = [
'type' => 'default',
'bg' => null,
];
// Parse type="highlight"
if (preg_match('/type=["\']([^"\']+)["\']/', $attr_string, $match)) {
$attributes['type'] = $match[1];
}
// Parse bg="url"
if (preg_match('/bg=["\']([^"\']+)["\']/', $attr_string, $match)) {
$attributes['bg'] = $match[1];
}
return $attributes;
}
/**
* Render a single card
*
* @param string $content
* @param array $attributes
* @return string
*/
private function render_card($content, $attributes) {
$type = $attributes['type'] ?? 'default';
$bg = $attributes['bg'] ?? null;
// Get email customization settings for colors
$email_settings = get_option('woonoow_email_settings', []);
$hero_gradient_start = $email_settings['hero_gradient_start'] ?? '#667eea';
$hero_gradient_end = $email_settings['hero_gradient_end'] ?? '#764ba2';
$hero_text_color = $email_settings['hero_text_color'] ?? '#ffffff';
$class = 'card';
$style = 'width: 100%; background-color: #ffffff; border-radius: 8px;';
$content_style = 'padding: 32px 40px;';
// Add type class and styling
if ($type !== 'default') {
$class .= ' card-' . esc_attr($type);
// Apply gradient and text color for hero/success cards
if ($type === 'hero' || $type === 'success') {
$style .= sprintf(
' background: linear-gradient(135deg, %s 0%%, %s 100%%);',
esc_attr($hero_gradient_start),
esc_attr($hero_gradient_end)
);
$content_style .= sprintf(' color: %s;', esc_attr($hero_text_color));
}
}
// Add background image
if ($bg) {
$class .= ' card-bg';
$style .= ' background-image: url(' . esc_url($bg) . ');';
}
return sprintf(
'<table role="presentation" class="%s" border="0" cellpadding="0" cellspacing="0" style="%s">
<tr>
<td class="content" style="%s">
%s
</td>
</tr>
</table>',
$class,
$style,
$content_style,
$content
);
}
/**
* Render card spacing
*
* @return string
*/
private function render_card_spacing() {
return '<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr><td class="card-spacing" style="height: 24px; font-size: 24px; line-height: 24px;">&nbsp;</td></tr>
</table>';
}
/**
* Replace variables in text
*
* @param string $text
* @param array $variables
* @return string
*/
private function replace_variables($text, $variables) {
foreach ($variables as $key => $value) {
$text = str_replace('{' . $key . '}', $value, $text);
}
return $text;
}
/**
* Get design template path
*
* @return string
*/
private function get_design_template() {
// Use single base template (theme-agnostic)
$template_path = WOONOOW_PATH . 'templates/emails/base.html';
// Allow filtering template path
$template_path = apply_filters('woonoow_email_template', $template_path);
// Fallback to base if custom template doesn't exist
if (!file_exists($template_path)) {
$template_path = WOONOOW_PATH . 'templates/emails/base.html';
}
return $template_path;
}
/**
* Render HTML email
*
* @param string $template_path Path to HTML template
* @param string $content Email content (HTML)
* @param string $subject Email subject
* @param array $variables All variables
* @return string
*/
private function render_html($template_path, $content, $subject, $variables) {
if (!file_exists($template_path)) {
// Fallback to plain HTML
return $content;
}
// Load template
$html = file_get_contents($template_path);
// Get email customization settings
$email_settings = get_option('woonoow_email_settings', []);
// Email body background
$body_bg = '#f8f8f8';
// Email header (logo or text)
if (!empty($email_settings['logo_url'])) {
$header = sprintf(
'<a href="%s"><img src="%s" alt="%s" style="max-width: 200px; max-height: 60px;"></a>',
esc_url($variables['store_url']),
esc_url($email_settings['logo_url']),
esc_attr($variables['store_name'])
);
} else {
$header_text = !empty($email_settings['header_text']) ? $email_settings['header_text'] : $variables['store_name'];
$header = sprintf(
'<a href="%s" style="font-size: 24px; font-weight: 700; color: #333; text-decoration: none;">%s</a>',
esc_url($variables['store_url']),
esc_html($header_text)
);
}
// Email footer with {current_year} variable support
$footer_text = !empty($email_settings['footer_text']) ? $email_settings['footer_text'] : sprintf(
'© %s %s. All rights reserved.',
date('Y'),
$variables['store_name']
);
// Replace {current_year} with actual year
$footer_text = str_replace('{current_year}', date('Y'), $footer_text);
// Social icons with PNG images
$social_html = '';
if (!empty($email_settings['social_links']) && is_array($email_settings['social_links'])) {
$icon_color = !empty($email_settings['social_icon_color']) ? $email_settings['social_icon_color'] : 'white';
$social_html = '<div class="social-icons" style="margin-top: 16px; text-align: center;">';
foreach ($email_settings['social_links'] as $link) {
if (!empty($link['url']) && !empty($link['platform'])) {
$icon_url = $this->get_social_icon_url($link['platform'], $icon_color);
$social_html .= sprintf(
'<a href="%s" style="display: inline-block; margin: 0 8px;"><img src="%s" alt="%s" style="width: 24px; height: 24px;"></a>',
esc_url($link['url']),
esc_url($icon_url),
esc_attr(ucfirst($link['platform']))
);
}
}
$social_html .= '</div>';
}
$footer = sprintf(
'<p style="font-family: \'Inter\', Arial, sans-serif; font-size: 13px; line-height: 1.5; color: #888888; margin: 0 0 8px 0; text-align: center;">%s</p>%s',
nl2br(esc_html($footer_text)),
$social_html
);
// Replace placeholders
$html = str_replace('{{email_subject}}', esc_html($subject), $html);
$html = str_replace('{{email_body_bg}}', esc_attr($body_bg), $html);
$html = str_replace('{{email_header}}', $header, $html);
$html = str_replace('{{email_content}}', $content, $html);
$html = str_replace('{{email_footer}}', $footer, $html);
$html = str_replace('{{store_name}}', esc_html($variables['store_name']), $html);
$html = str_replace('{{store_url}}', esc_url($variables['store_url']), $html);
$html = str_replace('{{current_year}}', date('Y'), $html);
// Replace all other variables
foreach ($variables as $key => $value) {
$html = str_replace('{{' . $key . '}}', $value, $html);
}
return $html;
}
/**
* Get social icon URL
*
* @param string $platform
* @param string $color 'white' or 'black'
* @return string
*/
private function get_social_icon_url($platform, $color = 'white') {
// Use local PNG icons
$plugin_url = plugin_dir_url(dirname(dirname(dirname(__FILE__))));
$filename = sprintf('mage--%s-%s.png', $platform, $color);
return $plugin_url . 'assets/icons/' . $filename;
}
}