🐛 Email Rendering Issues Fixed: 1. Markdown Not Parsed ❌ Raw markdown showing: ## Great news... ✅ Created MarkdownParser.php (PHP port from TypeScript) ✅ Parses headings, bold, italic, lists, links ✅ Supports card blocks and button syntax ✅ Proper newline handling 2. Variables Not Replaced ❌ {support_email} showing literally ✅ Added support_email variable ✅ Added current_year variable ✅ Added estimated_delivery variable (3-5 business days) 3. Broken Logo Image ❌ Broken image placeholder ✅ Fallback to site icon if no logo set ✅ Fallback to text header if no icon ✅ Proper URL handling 4. Newline Issues ❌ Variables on same line ✅ Markdown parser handles newlines correctly ✅ Proper paragraph wrapping 📦 New File: - includes/Core/Notifications/MarkdownParser.php - parse() - Convert markdown to HTML - parse_basics() - Parse standard markdown - nl2br_email() - Convert newlines for email 🔧 Updated Files: - EmailRenderer.php - Use MarkdownParser in render_card() - Add support_email, current_year variables - Add estimated_delivery calculation - Logo fallback to site icon - Text header fallback if no logo 🎯 Result: - ✅ Markdown properly rendered - ✅ All variables replaced - ✅ Logo displays (or text fallback) - ✅ Proper line breaks - ✅ Professional email appearance 📝 Example: Before: ## Great news, {customer_name}! After: <h2>Great news, Dwindi Ramadhana!</h2> Before: {support_email} After: admin@example.com Before: Broken image After: Site icon or store name
143 lines
3.9 KiB
PHP
143 lines
3.9 KiB
PHP
<?php
|
|
/**
|
|
* Markdown to Email HTML Parser
|
|
*
|
|
* PHP port of the TypeScript markdown parser from admin-spa
|
|
*
|
|
* Supports:
|
|
* - Standard Markdown (headings, bold, italic, lists, links, horizontal rules)
|
|
* - Card blocks with ::: syntax
|
|
* - Button blocks with [button url="..."]Text[/button] syntax
|
|
* - Variables with {variable_name}
|
|
* - Checkmarks (✓) and bullet points (•)
|
|
* - Proper newline handling
|
|
*
|
|
* @package WooNooW\Core\Notifications
|
|
*/
|
|
|
|
namespace WooNooW\Core\Notifications;
|
|
|
|
class MarkdownParser {
|
|
|
|
/**
|
|
* Parse markdown to email HTML
|
|
*
|
|
* @param string $markdown
|
|
* @return string
|
|
*/
|
|
public static function parse($markdown) {
|
|
$html = $markdown;
|
|
|
|
// Parse card blocks first (:::card or :::card[type])
|
|
$html = preg_replace_callback(
|
|
'/:::card(?:\[(\w+)\])?\n([\s\S]*?):::/s',
|
|
function($matches) {
|
|
$type = $matches[1] ?? '';
|
|
$content = trim($matches[2]);
|
|
$parsed_content = self::parse_basics($content);
|
|
return '[card' . ($type ? ' type="' . $type . '"' : '') . "]\n" . $parsed_content . "\n[/card]";
|
|
},
|
|
$html
|
|
);
|
|
|
|
// Parse button blocks [button url="..."]Text[/button] - already in correct format
|
|
// Also support legacy [button](url){text} syntax
|
|
$html = preg_replace_callback(
|
|
'/\[button(?:\s+style="(solid|outline)")?\]\((.*?)\)\s*\{([^}]+)\}/',
|
|
function($matches) {
|
|
$style = $matches[1] ?? '';
|
|
$url = $matches[2];
|
|
$text = $matches[3];
|
|
return '[button url="' . $url . '"' . ($style ? ' style="' . $style . '"' : '') . ']' . $text . '[/button]';
|
|
},
|
|
$html
|
|
);
|
|
|
|
// Horizontal rules
|
|
$html = preg_replace('/^---$/m', '<hr>', $html);
|
|
|
|
// Parse remaining markdown (outside cards)
|
|
$html = self::parse_basics($html);
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Parse basic markdown syntax
|
|
*
|
|
* @param string $text
|
|
* @return string
|
|
*/
|
|
private static function parse_basics($text) {
|
|
$html = $text;
|
|
|
|
// Headings (must be done in order from h4 to h1 to avoid conflicts)
|
|
$html = preg_replace('/^#### (.*)$/m', '<h4>$1</h4>', $html);
|
|
$html = preg_replace('/^### (.*)$/m', '<h3>$1</h3>', $html);
|
|
$html = preg_replace('/^## (.*)$/m', '<h2>$1</h2>', $html);
|
|
$html = preg_replace('/^# (.*)$/m', '<h1>$1</h1>', $html);
|
|
|
|
// Bold
|
|
$html = preg_replace('/\*\*(.*?)\*\*/s', '<strong>$1</strong>', $html);
|
|
$html = preg_replace('/__(.*?)__/s', '<strong>$1</strong>', $html);
|
|
|
|
// Italic
|
|
$html = preg_replace('/\*([^\*]+?)\*/', '<em>$1</em>', $html);
|
|
$html = preg_replace('/_([^_]+?)_/', '<em>$1</em>', $html);
|
|
|
|
// Links (but not button syntax)
|
|
$html = preg_replace('/\[(?!button)([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $html);
|
|
|
|
// Unordered lists (including checkmarks and bullets)
|
|
$html = preg_replace('/^[\*\-•✓✔] (.*)$/m', '<li>$1</li>', $html);
|
|
|
|
// Wrap consecutive <li> in <ul>
|
|
$html = preg_replace('/(<li>.*?<\/li>\s*)+/s', '<ul>$0</ul>', $html);
|
|
|
|
// Ordered lists
|
|
$html = preg_replace('/^\d+\. (.*)$/m', '<li>$1</li>', $html);
|
|
|
|
// Paragraphs (lines not already in tags)
|
|
$lines = explode("\n", $html);
|
|
$processed_lines = [];
|
|
|
|
foreach ($lines as $line) {
|
|
$trimmed = trim($line);
|
|
|
|
// Skip empty lines
|
|
if (empty($trimmed)) {
|
|
$processed_lines[] = '';
|
|
continue;
|
|
}
|
|
|
|
// Skip lines that are already HTML tags or shortcodes
|
|
if (preg_match('/^</', $trimmed) || preg_match('/^\[/', $trimmed)) {
|
|
$processed_lines[] = $line;
|
|
continue;
|
|
}
|
|
|
|
// Wrap in paragraph
|
|
$processed_lines[] = '<p>' . $line . '</p>';
|
|
}
|
|
|
|
$html = implode("\n", $processed_lines);
|
|
|
|
// Clean up extra newlines in HTML
|
|
$html = preg_replace('/\n{3,}/', "\n\n", $html);
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Convert newlines to <br> tags for email rendering
|
|
*
|
|
* @param string $html
|
|
* @return string
|
|
*/
|
|
public static function nl2br_email($html) {
|
|
// Don't convert newlines inside HTML tags
|
|
$html = preg_replace('/(?<!>)\n(?!<)/', '<br>', $html);
|
|
return $html;
|
|
}
|
|
}
|