fix: card ordering - process cards in document order

OLD BEHAVIOR (broken):
parse_cards processed ALL [card:type] syntax FIRST, then [card type=...]
This caused cards to render out of order when syntaxes were mixed.

NEW BEHAVIOR (fixed):
Using a unified regex that matches BOTH syntaxes simultaneously:
/\[card(?::(\w+)|([^\]]*)?)\](.*?)\[\/card\]/s

Each match includes:
- Group 1: Card type from new syntax [card:type]
- Group 2: Attributes from old syntax [card type='...']
- Group 3: Card content

Cards now render in exact document order regardless of syntax used.
This commit is contained in:
Dwindi Ramadhana
2026-01-01 23:57:12 +07:00
parent 802b64db9f
commit 930e525421

View File

@@ -288,45 +288,37 @@ class EmailRenderer {
* @return string
*/
private function parse_cards($content) {
$html = '';
$has_cards = false;
// Use a single unified regex to match BOTH syntaxes in document order
// This ensures cards are rendered in the order they appear
$combined_pattern = '/\[card(?::(\w+)|([^\]]*)?)\](.*?)\[\/card\]/s';
// Match NEW syntax [card:type]...[/card] first
preg_match_all('/\[card:(\w+)\](.*?)\[\/card\]/s', $content, $new_matches, PREG_SET_ORDER);
preg_match_all($combined_pattern, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
if (!empty($new_matches)) {
$has_cards = true;
foreach ($new_matches as $match) {
$attributes = ['type' => $match[1]];
$card_content = $match[2];
$html .= $this->render_card($card_content, $attributes);
$html .= $this->render_card_spacing();
// Remove matched content to avoid double processing
$content = str_replace($match[0], '', $content);
}
}
// Match OLD syntax [card type="..."]...[/card]
preg_match_all('/\[card([^\]]*)\](.*?)\[\/card\]/s', $content, $old_matches, PREG_SET_ORDER);
if (!empty($old_matches)) {
$has_cards = true;
foreach ($old_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();
}
}
if (!$has_cards) {
if (empty($matches)) {
// No cards found, wrap entire content in a single card
return $this->render_card($content, []);
}
$html = '';
foreach ($matches as $match) {
// Determine which syntax was matched
$full_match = $match[0][0];
$new_syntax_type = !empty($match[1][0]) ? $match[1][0] : null; // [card:type] format
$old_syntax_attrs = $match[2][0] ?? ''; // [card type="..."] format
$card_content = $match[3][0];
if ($new_syntax_type) {
// NEW syntax [card:type]
$attributes = ['type' => $new_syntax_type];
} else {
// OLD syntax [card type="..."] or [card]
$attributes = $this->parse_card_attributes($old_syntax_attrs);
}
$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);