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'),
'support_email' => get_option('admin_email'),
'current_year' => date('Y'),
];
// Order variables
if ($data instanceof \WC_Order) {
// Calculate estimated delivery (3-5 business days from now)
$estimated_delivery = date('F j', strtotime('+3 days')) . '-' . date('j', strtotime('+5 days'));
// Completion date (for completed orders)
$completion_date = '';
if ($data->get_date_completed()) {
$completion_date = $data->get_date_completed()->date('F j, Y');
} else {
$completion_date = date('F j, Y'); // Fallback to today
}
// Payment date
$payment_date = '';
if ($data->get_date_paid()) {
$payment_date = $data->get_date_paid()->date('F j, Y');
}
$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(),
'payment_status' => $data->get_status(),
'payment_date' => $payment_date,
'transaction_id' => $data->get_transaction_id() ?: 'N/A',
'shipping_method' => $data->get_shipping_method(),
'estimated_delivery' => $estimated_delivery,
'completion_date' => $completion_date,
'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(),
// URLs
'review_url' => $data->get_view_order_url(), // Can be customized later
'shop_url' => get_permalink(wc_get_page_id('shop')),
'my_account_url' => get_permalink(wc_get_page_id('myaccount')),
'payment_retry_url' => $data->get_checkout_payment_url(),
// Tracking (if available from meta)
'tracking_number' => $data->get_meta('_tracking_number') ?: 'N/A',
'tracking_url' => $data->get_meta('_tracking_url') ?: '#',
'shipping_carrier' => $data->get_meta('_shipping_carrier') ?: 'Standard Shipping',
]);
// Order items table
$items_html = '
';
$items_html .= '';
$items_html .= '| Product | ';
$items_html .= 'Qty | ';
$items_html .= 'Price | ';
$items_html .= '
';
foreach ($data->get_items() as $item) {
$product = $item->get_product();
$items_html .= '';
$items_html .= sprintf(
'| %s | ',
$item->get_name()
);
$items_html .= sprintf(
'%d | ',
$item->get_quantity()
);
$items_html .= sprintf(
'%s | ',
wc_price($item->get_total())
);
$items_html .= '
';
}
$items_html .= '
';
// Both naming conventions for compatibility
$variables['order_items'] = $items_html;
$variables['order_items_table'] = $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) {
// Generate password reset URL for new customers
$set_password_url = '';
$user = get_user_by('id', $data->get_id());
if ($user) {
$reset_key = get_password_reset_key($user);
if (!is_wp_error($reset_key)) {
$set_password_url = network_site_url(
"wp-login.php?action=rp&key={$reset_key}&login=" . rawurlencode($user->user_login),
'login'
);
}
}
$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(),
'set_password_url' => $set_password_url,
'my_account_url' => get_permalink(wc_get_page_id('myaccount')),
'shop_url' => get_permalink(wc_get_page_id('shop')),
]);
}
// 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('/]*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;
// Parse markdown in content
$content = MarkdownParser::parse($content);
// Get email customization settings for colors
$email_settings = get_option('woonoow_email_settings', []);
$primary_color = $email_settings['primary_color'] ?? '#7f54b3';
$secondary_color = $email_settings['secondary_color'] ?? '#7f54b3';
$button_text_color = $email_settings['button_text_color'] ?? '#ffffff';
$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';
// Parse button shortcodes with FULL INLINE STYLES for Gmail compatibility
// Format: [button url="..." style="solid|outline"]Text[/button]
$content = preg_replace_callback(
'/\[button\s+url=["\']([^"\']+)["\'](?:\s+style=["\']?(solid|outline)["\']?)?\]([^\[]+)\[\/button\]/',
function($matches) use ($primary_color, $secondary_color, $button_text_color) {
$url = $matches[1];
$style = $matches[2] ?? 'solid';
$text = trim($matches[3]);
if ($style === 'outline') {
// Outline button - transparent background with border
$button_style = sprintf(
'display: inline-block; background-color: transparent; color: %s; padding: 14px 28px; border: 2px solid %s; border-radius: 6px; text-decoration: none; font-weight: 600; font-family: "Inter", Arial, sans-serif; font-size: 16px; text-align: center; mso-padding-alt: 0;',
esc_attr($secondary_color),
esc_attr($secondary_color)
);
} else {
// Solid button - full background color
$button_style = sprintf(
'display: inline-block; background-color: %s; color: %s; padding: 14px 28px; border: none; border-radius: 6px; text-decoration: none; font-weight: 600; font-family: "Inter", Arial, sans-serif; font-size: 16px; text-align: center; mso-padding-alt: 0;',
esc_attr($primary_color),
esc_attr($button_text_color)
);
}
// Use table-based button for better email client compatibility
return sprintf(
'',
esc_url($url),
$button_style,
esc_html($text)
);
},
$content
);
$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 cards
if ($type === 'hero') {
$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 inline color to all headings and paragraphs for email client compatibility
$content = preg_replace(
'/<(h[1-6]|p)([^>]*)>/',
'<$1$2 style="color: ' . esc_attr($hero_text_color) . ';">',
$content
);
}
// Success card - green theme
elseif ($type === 'success') {
$style .= ' background-color: #f0fdf4; border-left: 4px solid #22c55e;';
}
// Info card - blue theme
elseif ($type === 'info') {
$style .= ' background-color: #f0f7ff; border-left: 4px solid #0071e3;';
}
// Warning card - orange theme
elseif ($type === 'warning') {
$style .= ' background-color: #fff8e1; border-left: 4px solid #ff9800;';
}
}
// Add background image
if ($bg) {
$class .= ' card-bg';
$style .= ' background-image: url(' . esc_url($bg) . ');';
}
return sprintf(
'',
$class,
$style,
$content_style,
$content
);
}
/**
* Render card spacing
*
* @return string
*/
private function render_card_spacing() {
return '';
}
/**
* 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)
$logo_url = $email_settings['logo_url'] ?? '';
// Fallback to site icon if no logo set
if (empty($logo_url) && has_site_icon()) {
$logo_url = get_site_icon_url(200);
}
if (!empty($logo_url)) {
$header = sprintf(
'
',
esc_url($variables['store_url']),
esc_url($logo_url),
esc_attr($variables['store_name'])
);
} else {
// No logo, use text header
$header_text = !empty($email_settings['header_text']) ? $email_settings['header_text'] : $variables['store_name'];
$header = sprintf(
'%s',
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 = '';
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(
'

',
esc_url($link['url']),
esc_url($icon_url),
esc_attr(ucfirst($link['platform']))
);
}
}
$social_html .= '
';
}
$footer = sprintf(
'%s
%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;
}
}