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 = [ 'site_name' => get_bloginfo('name'), 'site_title' => get_bloginfo('name'), 'store_name' => get_bloginfo('name'), 'store_url' => home_url(), 'shop_url' => get_permalink(wc_get_page_id('shop')), 'my_account_url' => get_permalink(wc_get_page_id('myaccount')), '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 .= ''; $items_html .= ''; $items_html .= ''; $items_html .= ''; foreach ($data->get_items() as $item) { $product = $item->get_product(); $items_html .= ''; $items_html .= sprintf( '', $item->get_name() ); $items_html .= sprintf( '', $item->get_quantity() ); $items_html .= sprintf( '', wc_price($item->get_total()) ); $items_html .= ''; } $items_html .= '
ProductQtyPrice
%s%d%s
'; // 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( '
%s
', 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( '%s', 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( '%s', 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 plugin URL constant if available, otherwise calculate from file path if (defined('WOONOOW_URL')) { $plugin_url = WOONOOW_URL; } else { // File is at includes/Core/Notifications/EmailRenderer.php - need 4 levels up $plugin_url = plugin_dir_url(dirname(dirname(dirname(dirname(__FILE__))))); } $filename = sprintf('mage--%s-%s.png', $platform, $color); return $plugin_url . 'assets/icons/' . $filename; } }