feat: Complete markdown syntax refinement and variable protection

 New cleaner syntax implemented:
- [card:type] instead of [card type='type']
- [button:style](url)Text[/button] instead of [button url='...' style='...']
- Standard markdown images: ![alt](url)

 Variable protection from markdown parsing:
- Variables with underscores (e.g., {order_items_table}) now protected
- HTML comment placeholders prevent italic/bold parsing
- All variables render correctly in preview

 Button rendering fixes:
- Buttons work in Visual mode inside cards
- Buttons work in Preview mode
- Button clicks prevented in visual editor
- Proper styling for solid and outline buttons

 Backward compatibility:
- Old syntax still supported
- No breaking changes

 Bug fixes:
- Fixed order_item_table → order_items_table naming
- Fixed button regex to match across newlines
- Added button/image parsing to parseMarkdownBasics
- Prevented button clicks on .button and .button-outline classes

📚 Documentation:
- NEW_MARKDOWN_SYNTAX.md - Complete user guide
- MARKDOWN_SYNTAX_AND_VARIABLES.md - Technical analysis
This commit is contained in:
dwindown
2025-11-15 20:05:50 +07:00
parent 550b3b69ef
commit 4471cd600f
45 changed files with 9194 additions and 508 deletions

View File

@@ -9,6 +9,8 @@
namespace WooNooW\Core\Notifications;
use WooNooW\Email\DefaultTemplates as EmailDefaultTemplates;
class TemplateProvider {
/**
@@ -35,12 +37,13 @@ class TemplateProvider {
*
* @param string $event_id Event ID
* @param string $channel_id Channel ID
* @param string $recipient_type Recipient type ('customer' or 'staff')
* @return array|null
*/
public static function get_template($event_id, $channel_id) {
public static function get_template($event_id, $channel_id, $recipient_type = 'customer') {
$templates = self::get_templates();
$key = "{$event_id}_{$channel_id}";
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
if (isset($templates[$key])) {
return $templates[$key];
@@ -48,7 +51,12 @@ class TemplateProvider {
// Return default if exists
$defaults = self::get_default_templates();
return $defaults[$key] ?? null;
if (isset($defaults[$key])) {
return $defaults[$key];
}
return null;
}
/**
@@ -57,16 +65,18 @@ class TemplateProvider {
* @param string $event_id Event ID
* @param string $channel_id Channel ID
* @param array $template Template data
* @param string $recipient_type Recipient type ('customer' or 'staff')
* @return bool
*/
public static function save_template($event_id, $channel_id, $template) {
public static function save_template($event_id, $channel_id, $template, $recipient_type = 'customer') {
$templates = get_option(self::OPTION_KEY, []);
$key = "{$event_id}_{$channel_id}";
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
$templates[$key] = [
'event_id' => $event_id,
'channel_id' => $channel_id,
'recipient_type' => $recipient_type,
'subject' => $template['subject'] ?? '',
'body' => $template['body'] ?? '',
'variables' => $template['variables'] ?? [],
@@ -81,12 +91,13 @@ class TemplateProvider {
*
* @param string $event_id Event ID
* @param string $channel_id Channel ID
* @param string $recipient_type Recipient type ('customer' or 'staff')
* @return bool
*/
public static function delete_template($event_id, $channel_id) {
public static function delete_template($event_id, $channel_id, $recipient_type = 'customer') {
$templates = get_option(self::OPTION_KEY, []);
$key = "{$event_id}_{$channel_id}";
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
if (isset($templates[$key])) {
unset($templates[$key]);
@@ -130,92 +141,104 @@ class TemplateProvider {
public static function get_default_templates() {
$templates = [];
// Define all events with their recipient types
$events = [
'order_placed' => 'staff',
'order_processing' => 'customer',
'order_completed' => 'customer',
'order_cancelled' => 'staff',
'order_refunded' => 'customer',
'low_stock' => 'staff',
'out_of_stock' => 'staff',
'new_customer' => 'customer',
'customer_note' => 'customer',
];
// Get all events from EventRegistry (single source of truth)
$all_events = EventRegistry::get_all_events();
// Generate email templates from DefaultEmailTemplates
foreach ($events as $event_id => $recipient_type) {
$default = DefaultEmailTemplates::get_template($event_id, $recipient_type);
// Get email templates from DefaultTemplates
$allEmailTemplates = EmailDefaultTemplates::get_all_templates();
foreach ($all_events as $event) {
$event_id = $event['id'];
$recipient_type = $event['recipient_type'];
// Get template body from the new clean markdown source
$body = $allEmailTemplates[$recipient_type][$event_id] ?? '';
$subject = EmailDefaultTemplates::get_default_subject($recipient_type, $event_id);
$templates["{$event_id}_email"] = [
// If template doesn't exist, create a simple fallback
if (empty($body)) {
$body = "[card]\n\n## Notification\n\nYou have a new notification about {$event_id}.\n\n[/card]";
$subject = __('Notification from {store_name}', 'woonoow');
}
$templates["{$recipient_type}_{$event_id}_email"] = [
'event_id' => $event_id,
'channel_id' => 'email',
'subject' => $default['subject'],
'body' => $default['body'],
'recipient_type' => $recipient_type,
'subject' => $subject,
'body' => $body,
'variables' => self::get_variables_for_event($event_id),
];
}
// Add push notification templates
$templates['order_placed_push'] = [
$templates['staff_order_placed_push'] = [
'event_id' => 'order_placed',
'channel_id' => 'push',
'recipient_type' => 'staff',
'subject' => __('New Order #{order_number}', 'woonoow'),
'body' => __('New order from {customer_name} - {order_total}', 'woonoow'),
'variables' => self::get_order_variables(),
];
$templates['order_processing_push'] = [
$templates['customer_order_processing_push'] = [
'event_id' => 'order_processing',
'channel_id' => 'push',
'recipient_type' => 'customer',
'subject' => __('Order Processing', 'woonoow'),
'body' => __('Your order #{order_number} is being processed', 'woonoow'),
'variables' => self::get_order_variables(),
];
$templates['order_completed_push'] = [
$templates['customer_order_completed_push'] = [
'event_id' => 'order_completed',
'channel_id' => 'push',
'recipient_type' => 'customer',
'subject' => __('Order Completed', 'woonoow'),
'body' => __('Your order #{order_number} has been completed!', 'woonoow'),
'variables' => self::get_order_variables(),
];
$templates['order_cancelled_push'] = [
$templates['staff_order_cancelled_push'] = [
'event_id' => 'order_cancelled',
'channel_id' => 'push',
'recipient_type' => 'staff',
'subject' => __('Order Cancelled', 'woonoow'),
'body' => __('Order #{order_number} has been cancelled', 'woonoow'),
'variables' => self::get_order_variables(),
];
$templates['order_refunded_push'] = [
$templates['customer_order_refunded_push'] = [
'event_id' => 'order_refunded',
'channel_id' => 'push',
'recipient_type' => 'customer',
'subject' => __('Order Refunded', 'woonoow'),
'body' => __('Your order #{order_number} has been refunded', 'woonoow'),
'variables' => self::get_order_variables(),
];
$templates['low_stock_push'] = [
$templates['staff_low_stock_push'] = [
'event_id' => 'low_stock',
'channel_id' => 'push',
'recipient_type' => 'staff',
'subject' => __('Low Stock Alert', 'woonoow'),
'body' => __('{product_name} is running low on stock', 'woonoow'),
'variables' => self::get_product_variables(),
];
$templates['out_of_stock_push'] = [
$templates['staff_out_of_stock_push'] = [
'event_id' => 'out_of_stock',
'channel_id' => 'push',
'recipient_type' => 'staff',
'subject' => __('Out of Stock Alert', 'woonoow'),
'body' => __('{product_name} is now out of stock', 'woonoow'),
'variables' => self::get_product_variables(),
];
$templates['new_customer_push'] = [
$templates['customer_new_customer_push'] = [
'event_id' => 'new_customer',
'channel_id' => 'push',
'recipient_type' => 'customer',
'subject' => __('Welcome!', 'woonoow'),
'body' => __('Welcome to {store_name}, {customer_name}!', 'woonoow'),
'variables' => self::get_customer_variables(),
];
$templates['customer_note_push'] = [
$templates['customer_customer_note_push'] = [
'event_id' => 'customer_note',
'channel_id' => 'push',
'recipient_type' => 'customer',
'subject' => __('Order Note Added', 'woonoow'),
'body' => __('A note has been added to order #{order_number}', 'woonoow'),
'variables' => self::get_order_variables(),