fix: resolve container width issues, spa redirects, and appearance settings overwrite. feat: enhance order/sub details and newsletter layout
This commit is contained in:
107
includes/Core/Notifications/ChannelRegistry.php
Normal file
107
includes/Core/Notifications/ChannelRegistry.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Channel Registry
|
||||
*
|
||||
* Manages registration and retrieval of notification channels
|
||||
*
|
||||
* @package WooNooW\Core\Notifications
|
||||
*/
|
||||
|
||||
namespace WooNooW\Core\Notifications;
|
||||
|
||||
use WooNooW\Core\Notifications\Channels\ChannelInterface;
|
||||
|
||||
class ChannelRegistry
|
||||
{
|
||||
|
||||
/**
|
||||
* Registered channels
|
||||
*
|
||||
* @var array<string, ChannelInterface>
|
||||
*/
|
||||
private static $channels = [];
|
||||
|
||||
/**
|
||||
* Register a notification channel
|
||||
*
|
||||
* @param ChannelInterface $channel Channel instance
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function register(ChannelInterface $channel)
|
||||
{
|
||||
$id = $channel->get_id();
|
||||
|
||||
if (empty($id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::$channels[$id] = $channel;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a registered channel by ID
|
||||
*
|
||||
* @param string $channel_id Channel identifier
|
||||
* @return ChannelInterface|null Channel instance or null if not found
|
||||
*/
|
||||
public static function get($channel_id)
|
||||
{
|
||||
return self::$channels[$channel_id] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered channels
|
||||
*
|
||||
* @return array<string, ChannelInterface> Associative array of channel_id => channel_instance
|
||||
*/
|
||||
public static function get_all()
|
||||
{
|
||||
return self::$channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a channel is registered
|
||||
*
|
||||
* @param string $channel_id Channel identifier
|
||||
* @return bool True if channel exists
|
||||
*/
|
||||
public static function has($channel_id)
|
||||
{
|
||||
return isset(self::$channels[$channel_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a channel
|
||||
*
|
||||
* @param string $channel_id Channel identifier
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function unregister($channel_id)
|
||||
{
|
||||
if (isset(self::$channels[$channel_id])) {
|
||||
unset(self::$channels[$channel_id]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of configured channel IDs
|
||||
*
|
||||
* Only returns channels that are properly configured (is_configured() returns true)
|
||||
*
|
||||
* @return array List of configured channel IDs
|
||||
*/
|
||||
public static function get_configured_channels()
|
||||
{
|
||||
$configured = [];
|
||||
foreach (self::$channels as $id => $channel) {
|
||||
if ($channel->is_configured()) {
|
||||
$configured[] = $id;
|
||||
}
|
||||
}
|
||||
return $configured;
|
||||
}
|
||||
}
|
||||
57
includes/Core/Notifications/Channels/ChannelInterface.php
Normal file
57
includes/Core/Notifications/Channels/ChannelInterface.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Channel Interface
|
||||
*
|
||||
* Contract for implementing custom notification channels (WhatsApp, SMS, Telegram, etc.)
|
||||
*
|
||||
* @package WooNooW\Core\Notifications\Channels
|
||||
*/
|
||||
|
||||
namespace WooNooW\Core\Notifications\Channels;
|
||||
|
||||
interface ChannelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get channel unique identifier
|
||||
*
|
||||
* @return string Channel ID (e.g., 'whatsapp', 'sms', 'telegram')
|
||||
*/
|
||||
public function get_id();
|
||||
|
||||
/**
|
||||
* Get channel display label
|
||||
*
|
||||
* @return string Channel label for UI (e.g., 'WhatsApp', 'SMS', 'Telegram')
|
||||
*/
|
||||
public function get_label();
|
||||
|
||||
/**
|
||||
* Check if channel is properly configured
|
||||
*
|
||||
* Example: API keys are set, credentials are valid, etc.
|
||||
*
|
||||
* @return bool True if channel is ready to send notifications
|
||||
*/
|
||||
public function is_configured();
|
||||
|
||||
/**
|
||||
* Send notification through this channel
|
||||
*
|
||||
* @param string $event_id Event identifier (e.g., 'order_completed', 'newsletter_confirm')
|
||||
* @param string $recipient Recipient type ('customer', 'staff')
|
||||
* @param array $data Notification context data (order, user, custom vars, etc.)
|
||||
* @return bool|array Success status, or array with 'success' and 'message' keys
|
||||
*/
|
||||
public function send($event_id, $recipient, $data);
|
||||
|
||||
/**
|
||||
* Get channel configuration fields for admin settings
|
||||
*
|
||||
* Optional. Returns array of field definitions for settings UI.
|
||||
*
|
||||
* @return array Field definitions (e.g., API key, sender number, etc.)
|
||||
*/
|
||||
public function get_config_fields();
|
||||
}
|
||||
252
includes/Core/Notifications/Channels/WhatsAppChannel.example.php
Normal file
252
includes/Core/Notifications/Channels/WhatsAppChannel.example.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* WhatsApp Channel - Example Implementation
|
||||
*
|
||||
* This is a reference implementation showing how to create a custom notification channel.
|
||||
* Developers can use this as a template for implementing WhatsApp, SMS, Telegram, etc.
|
||||
*
|
||||
* @package WooNooW\Core\Notifications\Channels
|
||||
*/
|
||||
|
||||
namespace WooNooW\Core\Notifications\Channels;
|
||||
|
||||
/**
|
||||
* Example WhatsApp Channel Implementation
|
||||
*
|
||||
* This channel sends notifications via WhatsApp Business API.
|
||||
* Replace API calls with your actual WhatsApp service provider (Twilio, MessageBird, etc.)
|
||||
*/
|
||||
class WhatsAppChannel implements ChannelInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Get channel ID
|
||||
*/
|
||||
public function get_id()
|
||||
{
|
||||
return 'whatsapp';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get channel label
|
||||
*/
|
||||
public function get_label()
|
||||
{
|
||||
return __('WhatsApp', 'woonoow');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if channel is configured
|
||||
*/
|
||||
public function is_configured()
|
||||
{
|
||||
$api_key = get_option('woonoow_whatsapp_api_key', '');
|
||||
$phone_number = get_option('woonoow_whatsapp_phone_number', '');
|
||||
|
||||
return !empty($api_key) && !empty($phone_number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send WhatsApp notification
|
||||
*
|
||||
* @param string $event_id Event identifier
|
||||
* @param string $recipient Recipient type ('customer' or 'staff')
|
||||
* @param array $data Context data (order, user, etc.)
|
||||
* @return bool|array Success status
|
||||
*/
|
||||
public function send($event_id, $recipient, $data)
|
||||
{
|
||||
// Get recipient phone number
|
||||
$phone = $this->get_recipient_phone($recipient, $data);
|
||||
|
||||
if (empty($phone)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'No phone number available for recipient',
|
||||
];
|
||||
}
|
||||
|
||||
// Build message content based on event
|
||||
$message = $this->build_message($event_id, $data);
|
||||
|
||||
if (empty($message)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Could not build message for event: ' . $event_id,
|
||||
];
|
||||
}
|
||||
|
||||
// Send via WhatsApp API
|
||||
$result = $this->send_whatsapp_message($phone, $message);
|
||||
|
||||
// Log the send attempt
|
||||
do_action('woonoow_whatsapp_sent', $event_id, $recipient, $phone, $result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration fields for admin settings
|
||||
*/
|
||||
public function get_config_fields()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'id' => 'woonoow_whatsapp_api_key',
|
||||
'label' => __('WhatsApp API Key', 'woonoow'),
|
||||
'type' => 'text',
|
||||
'description' => __('Your WhatsApp Business API key', 'woonoow'),
|
||||
],
|
||||
[
|
||||
'id' => 'woonoow_whatsapp_phone_number',
|
||||
'label' => __('WhatsApp Business Number', 'woonoow'),
|
||||
'type' => 'text',
|
||||
'description' => __('Your WhatsApp Business phone number (with country code)', 'woonoow'),
|
||||
'placeholder' => '+1234567890',
|
||||
],
|
||||
[
|
||||
'id' => 'woonoow_whatsapp_provider',
|
||||
'label' => __('Service Provider', 'woonoow'),
|
||||
'type' => 'select',
|
||||
'options' => [
|
||||
'twilio' => 'Twilio',
|
||||
'messagebird' => 'MessageBird',
|
||||
'custom' => 'Custom',
|
||||
],
|
||||
'default' => 'twilio',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recipient phone number
|
||||
*
|
||||
* @param string $recipient Recipient type
|
||||
* @param array $data Context data
|
||||
* @return string Phone number or empty string
|
||||
*/
|
||||
private function get_recipient_phone($recipient, $data)
|
||||
{
|
||||
if ($recipient === 'customer') {
|
||||
// Get customer phone from order or user data
|
||||
if (isset($data['order'])) {
|
||||
return $data['order']->get_billing_phone();
|
||||
}
|
||||
if (isset($data['user_id'])) {
|
||||
return get_user_meta($data['user_id'], 'billing_phone', true);
|
||||
}
|
||||
if (isset($data['email'])) {
|
||||
$user = get_user_by('email', $data['email']);
|
||||
if ($user) {
|
||||
return get_user_meta($user->ID, 'billing_phone', true);
|
||||
}
|
||||
}
|
||||
} elseif ($recipient === 'staff') {
|
||||
// Get admin phone from settings
|
||||
return get_option('woonoow_whatsapp_admin_phone', '');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build message content based on event
|
||||
*
|
||||
* @param string $event_id Event identifier
|
||||
* @param array $data Context data
|
||||
* @return string Message text
|
||||
*/
|
||||
private function build_message($event_id, $data)
|
||||
{
|
||||
// Allow filtering message content
|
||||
$message = apply_filters("woonoow_whatsapp_message_{$event_id}", '', $data);
|
||||
|
||||
if (!empty($message)) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
// Default messages for common events
|
||||
$site_name = get_bloginfo('name');
|
||||
|
||||
switch ($event_id) {
|
||||
case 'order_completed':
|
||||
if (isset($data['order'])) {
|
||||
$order = $data['order'];
|
||||
return sprintf(
|
||||
"🎉 Your order #%s has been completed! Thank you for shopping with %s.",
|
||||
$order->get_order_number(),
|
||||
$site_name
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'newsletter_confirm':
|
||||
if (isset($data['confirmation_url'])) {
|
||||
return sprintf(
|
||||
"Please confirm your newsletter subscription by clicking: %s",
|
||||
$data['confirmation_url']
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
// Add more event templates as needed
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send WhatsApp message via API
|
||||
*
|
||||
* Replace this with actual API integration for your provider
|
||||
*
|
||||
* @param string $phone Recipient phone number
|
||||
* @param string $message Message text
|
||||
* @return array Result with 'success' and 'message' keys
|
||||
*/
|
||||
private function send_whatsapp_message($phone, $message)
|
||||
{
|
||||
$api_key = get_option('woonoow_whatsapp_api_key', '');
|
||||
$from_number = get_option('woonoow_whatsapp_phone_number', '');
|
||||
$provider = get_option('woonoow_whatsapp_provider', 'twilio');
|
||||
|
||||
// Example: Twilio API (replace with your actual implementation)
|
||||
if ($provider === 'twilio') {
|
||||
$endpoint = 'https://api.twilio.com/2010-04-01/Accounts/YOUR_ACCOUNT_SID/Messages.json';
|
||||
|
||||
$response = wp_remote_post($endpoint, [
|
||||
'headers' => [
|
||||
'Authorization' => 'Basic ' . base64_encode($api_key),
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
],
|
||||
'body' => [
|
||||
'From' => 'whatsapp:' . $from_number,
|
||||
'To' => 'whatsapp:' . $phone,
|
||||
'Body' => $message,
|
||||
],
|
||||
]);
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $response->get_error_message(),
|
||||
];
|
||||
}
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code($response);
|
||||
return [
|
||||
'success' => $status_code >= 200 && $status_code < 300,
|
||||
'message' => $status_code >= 200 && $status_code < 300
|
||||
? 'WhatsApp message sent successfully'
|
||||
: 'Failed to send WhatsApp message',
|
||||
];
|
||||
}
|
||||
|
||||
// For custom providers, implement your own logic here
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => 'Provider not configured',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Default Email Templates (DEPRECATED)
|
||||
*
|
||||
@@ -17,8 +18,9 @@ namespace WooNooW\Core\Notifications;
|
||||
|
||||
use WooNooW\Email\DefaultTemplates as NewDefaultTemplates;
|
||||
|
||||
class DefaultEmailTemplates {
|
||||
|
||||
class DefaultEmailTemplates
|
||||
{
|
||||
|
||||
/**
|
||||
* Get default template for an event and recipient type
|
||||
*
|
||||
@@ -26,28 +28,30 @@ class DefaultEmailTemplates {
|
||||
* @param string $recipient_type 'staff' or 'customer'
|
||||
* @return array ['subject' => string, 'body' => string]
|
||||
*/
|
||||
public static function get_template($event_id, $recipient_type) {
|
||||
public static function get_template($event_id, $recipient_type)
|
||||
{
|
||||
// Get templates directly from this class
|
||||
$allTemplates = self::get_all_templates();
|
||||
|
||||
|
||||
// Check if event exists for this recipient type
|
||||
if (isset($allTemplates[$event_id][$recipient_type])) {
|
||||
return $allTemplates[$event_id][$recipient_type];
|
||||
}
|
||||
|
||||
|
||||
// Fallback
|
||||
return [
|
||||
'subject' => __('Notification from {store_name}', 'woonoow'),
|
||||
'body' => '[card]' . __('You have a new notification.', 'woonoow') . '[/card]',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all default templates (legacy method - kept for backwards compatibility)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_all_templates() {
|
||||
private static function get_all_templates()
|
||||
{
|
||||
// This method is now deprecated but kept for backwards compatibility
|
||||
// Use WooNooW\Email\DefaultTemplates instead
|
||||
return [
|
||||
@@ -83,7 +87,7 @@ class DefaultEmailTemplates {
|
||||
[button url="{order_url}" style="solid"]' . __('View Order Details', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
'order_processing' => [
|
||||
'customer' => [
|
||||
'subject' => __('Your Order #{order_number} is Being Processed', 'woonoow'),
|
||||
@@ -112,7 +116,7 @@ class DefaultEmailTemplates {
|
||||
[button url="{order_url}" style="solid"]' . __('Track Your Order', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
'order_completed' => [
|
||||
'customer' => [
|
||||
'subject' => __('Your Order #{order_number} is Complete', 'woonoow'),
|
||||
@@ -135,10 +139,10 @@ class DefaultEmailTemplates {
|
||||
[/card]
|
||||
|
||||
[button url="{order_url}" style="solid"]' . __('View Order', 'woonoow') . '[/button]
|
||||
[button url="{store_url}" style="outline"]' . __('Continue Shopping', 'woonoow') . '[/button]',
|
||||
[button url="{shop_url}" style="outline"]' . __('Continue Shopping', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
'order_cancelled' => [
|
||||
'staff' => [
|
||||
'subject' => __('Order #{order_number} Cancelled', 'woonoow'),
|
||||
@@ -158,7 +162,7 @@ class DefaultEmailTemplates {
|
||||
[button url="{order_url}" style="solid"]' . __('View Order Details', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
'order_refunded' => [
|
||||
'customer' => [
|
||||
'subject' => __('Your Order #{order_number} Has Been Refunded', 'woonoow'),
|
||||
@@ -183,7 +187,7 @@ class DefaultEmailTemplates {
|
||||
[button url="{order_url}" style="solid"]' . __('View Order', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
// PRODUCT EVENTS
|
||||
'low_stock' => [
|
||||
'staff' => [
|
||||
@@ -209,7 +213,7 @@ class DefaultEmailTemplates {
|
||||
[button url="{product_url}" style="solid"]' . __('View Product', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
'out_of_stock' => [
|
||||
'staff' => [
|
||||
'subject' => __('Out of Stock Alert: {product_name}', 'woonoow'),
|
||||
@@ -233,7 +237,7 @@ class DefaultEmailTemplates {
|
||||
[button url="{product_url}" style="solid"]' . __('Manage Product', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
// CUSTOMER EVENTS
|
||||
'new_customer' => [
|
||||
'customer' => [
|
||||
@@ -261,10 +265,10 @@ class DefaultEmailTemplates {
|
||||
[/card]
|
||||
|
||||
[button url="{account_url}" style="solid"]' . __('Go to My Account', 'woonoow') . '[/button]
|
||||
[button url="{store_url}" style="outline"]' . __('Start Shopping', 'woonoow') . '[/button]',
|
||||
[button url="{shop_url}" style="outline"]' . __('Start Shopping', 'woonoow') . '[/button]',
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
'customer_note' => [
|
||||
'customer' => [
|
||||
'subject' => __('Note Added to Your Order #{order_number}', 'woonoow'),
|
||||
@@ -289,16 +293,17 @@ class DefaultEmailTemplates {
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all new templates (direct access to new class)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_new_templates() {
|
||||
public static function get_new_templates()
|
||||
{
|
||||
return NewDefaultTemplates::get_all_templates();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get default subject from new templates
|
||||
*
|
||||
@@ -306,7 +311,8 @@ class DefaultEmailTemplates {
|
||||
* @param string $event_id Event ID
|
||||
* @return string
|
||||
*/
|
||||
public static function get_default_subject($recipient_type, $event_id) {
|
||||
public static function get_default_subject($recipient_type, $event_id)
|
||||
{
|
||||
return NewDefaultTemplates::get_default_subject($recipient_type, $event_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class EmailRenderer
|
||||
* @param string $recipient_type
|
||||
* @return array|null
|
||||
*/
|
||||
private function get_template_settings($event_id, $recipient_type)
|
||||
public 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);
|
||||
@@ -187,7 +187,7 @@ class EmailRenderer
|
||||
'site_name' => get_bloginfo('name'),
|
||||
'site_title' => get_bloginfo('name'),
|
||||
'store_name' => get_bloginfo('name'),
|
||||
'store_url' => home_url(),
|
||||
'site_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'),
|
||||
@@ -381,7 +381,7 @@ class EmailRenderer
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
private function parse_cards($content)
|
||||
public function parse_cards($content)
|
||||
{
|
||||
// Use a single unified regex to match BOTH syntaxes in document order
|
||||
// This ensures cards are rendered in the order they appear
|
||||
@@ -473,8 +473,31 @@ class EmailRenderer
|
||||
$hero_text_color = '#ffffff'; // Always white on gradient
|
||||
|
||||
// Parse button shortcodes with FULL INLINE STYLES for Gmail compatibility
|
||||
// Helper function to escape URL while preserving variable placeholders like {unsubscribe_url}
|
||||
$escape_url_preserving_variables = function ($url) {
|
||||
// If URL contains variable placeholder, don't escape (will be replaced later)
|
||||
if (preg_match('/\{[a-z_]+\}/', $url)) {
|
||||
// Just return the URL as-is - it will be replaced with a real URL later
|
||||
return $url;
|
||||
}
|
||||
return esc_url($url);
|
||||
};
|
||||
|
||||
// Helper function to generate button HTML
|
||||
$generateButtonHtml = function ($url, $style, $text) use ($primary_color, $secondary_color, $button_text_color) {
|
||||
$generateButtonHtml = function ($url, $style, $text) use ($primary_color, $secondary_color, $button_text_color, $escape_url_preserving_variables) {
|
||||
$escaped_url = $escape_url_preserving_variables($url);
|
||||
|
||||
if ($style === 'link') {
|
||||
// Plain link - just a simple <a> tag styled like regular text link (inline, no wrapper)
|
||||
return sprintf(
|
||||
'<a href="%s" style="color: %s; text-decoration: underline; font-family: \'Inter\', Arial, sans-serif;">%s</a>',
|
||||
$escaped_url,
|
||||
esc_attr($primary_color),
|
||||
esc_html($text)
|
||||
);
|
||||
}
|
||||
|
||||
// Styled buttons (solid/outline) get table wrapper for email client compatibility
|
||||
if ($style === 'outline') {
|
||||
// Outline button - transparent background with border
|
||||
$button_style = sprintf(
|
||||
@@ -494,7 +517,7 @@ class EmailRenderer
|
||||
// Use table-based button for better email client compatibility
|
||||
return sprintf(
|
||||
'<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin: 16px auto;"><tr><td align="center"><a href="%s" style="%s">%s</a></td></tr></table>',
|
||||
esc_url($url),
|
||||
$escaped_url,
|
||||
$button_style,
|
||||
esc_html($text)
|
||||
);
|
||||
@@ -542,9 +565,25 @@ class EmailRenderer
|
||||
$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) . ';">',
|
||||
// Preserve existing style attributes (like text-align) by appending to them
|
||||
$content = preg_replace_callback(
|
||||
'/<(h[1-6]|p)([^>]*?)(\s+style=["\']([^"\']*)["\'])?([^>]*)>/',
|
||||
function ($matches) use ($hero_text_color) {
|
||||
$tag = $matches[1];
|
||||
$before_style = $matches[2];
|
||||
$existing_style = isset($matches[4]) ? $matches[4] : '';
|
||||
$after_style = $matches[5];
|
||||
$color_style = 'color: ' . esc_attr($hero_text_color) . ';';
|
||||
|
||||
if ($existing_style) {
|
||||
// Append to existing style
|
||||
$new_style = rtrim($existing_style, ';') . '; ' . $color_style;
|
||||
return '<' . $tag . $before_style . ' style="' . $new_style . '"' . $after_style . '>';
|
||||
} else {
|
||||
// Add new style attribute
|
||||
return '<' . $tag . $before_style . ' style="' . $color_style . '"' . $after_style . '>';
|
||||
}
|
||||
},
|
||||
$content
|
||||
);
|
||||
}
|
||||
@@ -560,6 +599,11 @@ class EmailRenderer
|
||||
elseif ($type === 'warning') {
|
||||
$style .= ' background-color: #fff8e1;';
|
||||
}
|
||||
// Basic card - plain text, no card styling (for footers/muted content)
|
||||
elseif ($type === 'basic') {
|
||||
$style = 'width: 100%; background-color: transparent;'; // No background
|
||||
$content_style = 'padding: 0;'; // No padding
|
||||
}
|
||||
}
|
||||
|
||||
// Add background image
|
||||
@@ -616,7 +660,7 @@ class EmailRenderer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_design_template()
|
||||
public function get_design_template()
|
||||
{
|
||||
// Use single base template (theme-agnostic)
|
||||
$template_path = WOONOOW_PATH . 'templates/emails/base.html';
|
||||
@@ -641,7 +685,7 @@ class EmailRenderer
|
||||
* @param array $variables All variables
|
||||
* @return string
|
||||
*/
|
||||
private function render_html($template_path, $content, $subject, $variables)
|
||||
public function render_html($template_path, $content, $subject, $variables)
|
||||
{
|
||||
if (!file_exists($template_path)) {
|
||||
// Fallback to plain HTML
|
||||
@@ -654,6 +698,10 @@ class EmailRenderer
|
||||
// Get email customization settings
|
||||
$email_settings = get_option('woonoow_email_settings', []);
|
||||
|
||||
// Ensure required variables have defaults
|
||||
$variables['site_url'] = $variables['site_url'] ?? home_url();
|
||||
$variables['store_name'] = $variables['store_name'] ?? get_bloginfo('name');
|
||||
|
||||
// Email body background
|
||||
$body_bg = '#f8f8f8';
|
||||
|
||||
@@ -668,7 +716,7 @@ class EmailRenderer
|
||||
if (!empty($logo_url)) {
|
||||
$header = sprintf(
|
||||
'<a href="%s"><img src="%s" alt="%s" style="max-width: 200px; max-height: 60px;"></a>',
|
||||
esc_url($variables['store_url']),
|
||||
esc_url($variables['site_url']),
|
||||
esc_url($logo_url),
|
||||
esc_attr($variables['store_name'])
|
||||
);
|
||||
@@ -677,7 +725,7 @@ class EmailRenderer
|
||||
$header_text = !empty($email_settings['header_text']) ? $email_settings['header_text'] : $variables['store_name'];
|
||||
$header = sprintf(
|
||||
'<a href="%s" style="font-size: 24px; font-weight: 700; color: #333; text-decoration: none;">%s</a>',
|
||||
esc_url($variables['store_url']),
|
||||
esc_url($variables['site_url']),
|
||||
esc_html($header_text)
|
||||
);
|
||||
}
|
||||
@@ -724,7 +772,7 @@ class EmailRenderer
|
||||
$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('{{site_url}}', esc_url($variables['site_url']), $html);
|
||||
$html = str_replace('{{current_year}}', date('Y'), $html);
|
||||
|
||||
// Replace all other variables
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Markdown to Email HTML Parser
|
||||
*
|
||||
@@ -17,21 +18,23 @@
|
||||
|
||||
namespace WooNooW\Core\Notifications;
|
||||
|
||||
class MarkdownParser {
|
||||
|
||||
class MarkdownParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Parse markdown to email HTML
|
||||
*
|
||||
* @param string $markdown
|
||||
* @return string
|
||||
*/
|
||||
public static function parse($markdown) {
|
||||
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) {
|
||||
function ($matches) {
|
||||
$type = $matches[1] ?? '';
|
||||
$content = trim($matches[2]);
|
||||
$parsed_content = self::parse_basics($content);
|
||||
@@ -39,12 +42,12 @@ class MarkdownParser {
|
||||
},
|
||||
$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) {
|
||||
function ($matches) {
|
||||
$style = $matches[1] ?? '';
|
||||
$url = $matches[2];
|
||||
$text = $matches[3];
|
||||
@@ -52,71 +55,88 @@ class MarkdownParser {
|
||||
},
|
||||
$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) {
|
||||
private static function parse_basics($text)
|
||||
{
|
||||
$html = $text;
|
||||
|
||||
|
||||
// Protect variables from markdown parsing by temporarily replacing them
|
||||
$variables = [];
|
||||
$var_index = 0;
|
||||
$html = preg_replace_callback('/\{([^}]+)\}/', function($matches) use (&$variables, &$var_index) {
|
||||
$html = preg_replace_callback('/\{([^}]+)\}/', function ($matches) use (&$variables, &$var_index) {
|
||||
$placeholder = '<!--VAR' . $var_index . '-->';
|
||||
$variables[$placeholder] = $matches[0];
|
||||
$var_index++;
|
||||
return $placeholder;
|
||||
}, $html);
|
||||
|
||||
|
||||
// Protect existing HTML tags (h1-h6, p) with style attributes from being overwritten
|
||||
$html_tags = [];
|
||||
$tag_index = 0;
|
||||
$html = preg_replace_callback('/<(h[1-6]|p)([^>]*style=[^>]*)>/', function ($matches) use (&$html_tags, &$tag_index) {
|
||||
$placeholder = '<!--HTMLTAG' . $tag_index . '-->';
|
||||
$html_tags[$placeholder] = $matches[0];
|
||||
$tag_index++;
|
||||
return $placeholder;
|
||||
}, $html);
|
||||
|
||||
// Headings (must be done in order from h4 to h1 to avoid conflicts)
|
||||
// Only match markdown syntax (lines starting with #), not existing HTML
|
||||
$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);
|
||||
|
||||
|
||||
// Restore protected HTML tags
|
||||
foreach ($html_tags as $placeholder => $original) {
|
||||
$html = str_replace($placeholder, $original, $html);
|
||||
}
|
||||
|
||||
// Bold (don't match across newlines)
|
||||
$html = preg_replace('/\*\*([^\n*]+?)\*\*/', '<strong>$1</strong>', $html);
|
||||
$html = preg_replace('/__([^\n_]+?)__/', '<strong>$1</strong>', $html);
|
||||
|
||||
|
||||
// Italic (don't match across newlines)
|
||||
$html = preg_replace('/\*([^\n*]+?)\*/', '<em>$1</em>', $html);
|
||||
$html = preg_replace('/_([^\n_]+?)_/', '<em>$1</em>', $html);
|
||||
|
||||
|
||||
// Horizontal rules
|
||||
$html = preg_replace('/^---$/m', '<hr>', $html);
|
||||
|
||||
|
||||
// Links (but not button syntax)
|
||||
$html = preg_replace('/\[(?!button)([^\]]+)\]\(([^)]+)\)/', '<a href="$2">$1</a>', $html);
|
||||
|
||||
|
||||
// Process lines for paragraphs and lists
|
||||
$lines = explode("\n", $html);
|
||||
$in_list = false;
|
||||
$paragraph_content = '';
|
||||
$processed_lines = [];
|
||||
|
||||
$close_paragraph = function() use (&$paragraph_content, &$processed_lines) {
|
||||
|
||||
$close_paragraph = function () use (&$paragraph_content, &$processed_lines) {
|
||||
if ($paragraph_content) {
|
||||
$processed_lines[] = '<p>' . $paragraph_content . '</p>';
|
||||
$paragraph_content = '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$trimmed = trim($line);
|
||||
|
||||
|
||||
// Empty line - close paragraph or list
|
||||
if (empty($trimmed)) {
|
||||
if ($in_list) {
|
||||
@@ -127,7 +147,7 @@ class MarkdownParser {
|
||||
$processed_lines[] = '';
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Check if line is a list item
|
||||
if (preg_match('/^[\*\-•✓✔]\s/', $trimmed)) {
|
||||
$close_paragraph();
|
||||
@@ -139,20 +159,20 @@ class MarkdownParser {
|
||||
$processed_lines[] = '<li>' . $content . '</li>';
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Close list if we're in one
|
||||
if ($in_list) {
|
||||
$processed_lines[] = '</ul>';
|
||||
$in_list = false;
|
||||
}
|
||||
|
||||
|
||||
// Block-level HTML tags - don't wrap in paragraph
|
||||
if (preg_match('/^<(div|h1|h2|h3|h4|h5|h6|p|ul|ol|li|hr|table|blockquote)/i', $trimmed)) {
|
||||
$close_paragraph();
|
||||
$processed_lines[] = $line;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Regular text line - accumulate in paragraph
|
||||
if ($paragraph_content) {
|
||||
// Add line break before continuation (THIS IS THE KEY FIX!)
|
||||
@@ -162,30 +182,31 @@ class MarkdownParser {
|
||||
$paragraph_content = $trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Close any open tags
|
||||
if ($in_list) {
|
||||
$processed_lines[] = '</ul>';
|
||||
}
|
||||
$close_paragraph();
|
||||
|
||||
|
||||
$html = implode("\n", $processed_lines);
|
||||
|
||||
|
||||
// Restore variables
|
||||
foreach ($variables as $placeholder => $original) {
|
||||
$html = str_replace($placeholder, $original, $html);
|
||||
}
|
||||
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert newlines to <br> tags for email rendering
|
||||
*
|
||||
* @param string $html
|
||||
* @return string
|
||||
*/
|
||||
public static function nl2br_email($html) {
|
||||
public static function nl2br_email($html)
|
||||
{
|
||||
// Don't convert newlines inside HTML tags
|
||||
$html = preg_replace('/(?<!>)\n(?!<)/', '<br>', $html);
|
||||
return $html;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Notification Manager
|
||||
*
|
||||
@@ -9,32 +10,43 @@
|
||||
|
||||
namespace WooNooW\Core\Notifications;
|
||||
|
||||
class NotificationManager {
|
||||
|
||||
use WooNooW\Core\Notifications\ChannelRegistry;
|
||||
|
||||
class NotificationManager
|
||||
{
|
||||
|
||||
/**
|
||||
* Check if a channel is enabled globally
|
||||
*
|
||||
* @param string $channel_id Channel ID (email, push, etc.)
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_channel_enabled($channel_id) {
|
||||
public static function is_channel_enabled($channel_id)
|
||||
{
|
||||
// Check built-in channels
|
||||
if ($channel_id === 'email') {
|
||||
return (bool) get_option('woonoow_email_notifications_enabled', true);
|
||||
} elseif ($channel_id === 'push') {
|
||||
return (bool) get_option('woonoow_push_notifications_enabled', true);
|
||||
}
|
||||
|
||||
// For addon channels, check if they're registered and enabled
|
||||
|
||||
// Check if channel is registered in ChannelRegistry
|
||||
if (ChannelRegistry::has($channel_id)) {
|
||||
$channel = ChannelRegistry::get($channel_id);
|
||||
return $channel->is_configured();
|
||||
}
|
||||
|
||||
// Legacy: check via filter (backward compatibility)
|
||||
$channels = apply_filters('woonoow_notification_channels', []);
|
||||
foreach ($channels as $channel) {
|
||||
if ($channel['id'] === $channel_id) {
|
||||
return isset($channel['enabled']) ? (bool) $channel['enabled'] : true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a channel is enabled for a specific event
|
||||
*
|
||||
@@ -42,24 +54,25 @@ class NotificationManager {
|
||||
* @param string $channel_id Channel ID
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_event_channel_enabled($event_id, $channel_id) {
|
||||
public static function is_event_channel_enabled($event_id, $channel_id)
|
||||
{
|
||||
$settings = get_option('woonoow_notification_settings', []);
|
||||
|
||||
|
||||
if (!isset($settings[$event_id])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$event = $settings[$event_id];
|
||||
|
||||
|
||||
if (!isset($event['channels'][$channel_id])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($event['channels'][$channel_id]['enabled'])
|
||||
? (bool) $event['channels'][$channel_id]['enabled']
|
||||
|
||||
return isset($event['channels'][$channel_id]['enabled'])
|
||||
? (bool) $event['channels'][$channel_id]['enabled']
|
||||
: false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if notification should be sent
|
||||
*
|
||||
@@ -69,26 +82,27 @@ class NotificationManager {
|
||||
* @param string $channel_id Channel ID
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_send_notification($event_id, $channel_id) {
|
||||
public static function should_send_notification($event_id, $channel_id)
|
||||
{
|
||||
// Check if WooNooW notification system is enabled
|
||||
$system_mode = get_option('woonoow_notification_system_mode', 'woonoow');
|
||||
if ($system_mode !== 'woonoow') {
|
||||
return false; // Use WooCommerce default emails instead
|
||||
}
|
||||
|
||||
|
||||
// Check if channel is globally enabled
|
||||
if (!self::is_channel_enabled($channel_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check if channel is enabled for this specific event
|
||||
if (!self::is_event_channel_enabled($event_id, $channel_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get recipient for event channel
|
||||
*
|
||||
@@ -96,16 +110,17 @@ class NotificationManager {
|
||||
* @param string $channel_id Channel ID
|
||||
* @return string Recipient type (admin, customer, both)
|
||||
*/
|
||||
public static function get_recipient($event_id, $channel_id) {
|
||||
public static function get_recipient($event_id, $channel_id)
|
||||
{
|
||||
$settings = get_option('woonoow_notification_settings', []);
|
||||
|
||||
|
||||
if (!isset($settings[$event_id]['channels'][$channel_id]['recipient'])) {
|
||||
return 'admin';
|
||||
}
|
||||
|
||||
|
||||
return $settings[$event_id]['channels'][$channel_id]['recipient'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send notification through specified channel
|
||||
*
|
||||
@@ -114,16 +129,25 @@ class NotificationManager {
|
||||
* @param array $data Notification data
|
||||
* @return bool Success status
|
||||
*/
|
||||
public static function send($event_id, $channel_id, $data = []) {
|
||||
public static function send($event_id, $channel_id, $data = [])
|
||||
{
|
||||
// Validate if notification should be sent
|
||||
if (!self::should_send_notification($event_id, $channel_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Get recipient
|
||||
$recipient = self::get_recipient($event_id, $channel_id);
|
||||
|
||||
// Allow addons to handle their own channels
|
||||
|
||||
// Try to use registered channel from ChannelRegistry
|
||||
if (ChannelRegistry::has($channel_id)) {
|
||||
$channel = ChannelRegistry::get($channel_id);
|
||||
if ($channel->is_configured()) {
|
||||
return $channel->send($event_id, $recipient, $data);
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy: Allow addons to handle their own channels via filter
|
||||
$sent = apply_filters(
|
||||
'woonoow_send_notification',
|
||||
false,
|
||||
@@ -132,22 +156,22 @@ class NotificationManager {
|
||||
$recipient,
|
||||
$data
|
||||
);
|
||||
|
||||
|
||||
// If addon handled it, return
|
||||
if ($sent !== false) {
|
||||
return $sent;
|
||||
}
|
||||
|
||||
// Handle built-in channels
|
||||
|
||||
// Handle built-in channels (email, push)
|
||||
if ($channel_id === 'email') {
|
||||
return self::send_email($event_id, $recipient, $data);
|
||||
} elseif ($channel_id === 'push') {
|
||||
return self::send_push($event_id, $recipient, $data);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send email notification
|
||||
*
|
||||
@@ -156,25 +180,26 @@ class NotificationManager {
|
||||
* @param array $data Notification data
|
||||
* @return bool
|
||||
*/
|
||||
private static function send_email($event_id, $recipient, $data) {
|
||||
private static function send_email($event_id, $recipient, $data)
|
||||
{
|
||||
// Use EmailRenderer to render the email
|
||||
$renderer = EmailRenderer::instance();
|
||||
$email_data = $renderer->render($event_id, $recipient, $data['order'] ?? $data['product'] ?? $data['customer'] ?? null, $data);
|
||||
|
||||
|
||||
if (!$email_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Send email using wp_mail
|
||||
$headers = ['Content-Type: text/html; charset=UTF-8'];
|
||||
$sent = wp_mail($email_data['to'], $email_data['subject'], $email_data['body'], $headers);
|
||||
|
||||
|
||||
// Trigger action for logging/tracking
|
||||
do_action('woonoow_email_sent', $event_id, $recipient, $email_data, $sent);
|
||||
|
||||
|
||||
return $sent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send push notification
|
||||
*
|
||||
@@ -183,7 +208,8 @@ class NotificationManager {
|
||||
* @param array $data Notification data
|
||||
* @return bool
|
||||
*/
|
||||
private static function send_push($event_id, $recipient, $data) {
|
||||
private static function send_push($event_id, $recipient, $data)
|
||||
{
|
||||
// Push notification sending will be implemented later
|
||||
// This is a placeholder for future implementation
|
||||
do_action('woonoow_send_push_notification', $event_id, $recipient, $data);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Notification Template Provider
|
||||
*
|
||||
@@ -11,27 +12,29 @@ namespace WooNooW\Core\Notifications;
|
||||
|
||||
use WooNooW\Email\DefaultTemplates as EmailDefaultTemplates;
|
||||
|
||||
class TemplateProvider {
|
||||
|
||||
class TemplateProvider
|
||||
{
|
||||
|
||||
/**
|
||||
* Option key for storing templates
|
||||
*/
|
||||
const OPTION_KEY = 'woonoow_notification_templates';
|
||||
|
||||
|
||||
/**
|
||||
* Get all templates
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_templates() {
|
||||
public static function get_templates()
|
||||
{
|
||||
$templates = get_option(self::OPTION_KEY, []);
|
||||
|
||||
|
||||
// Merge with defaults
|
||||
$defaults = self::get_default_templates();
|
||||
|
||||
|
||||
return array_merge($defaults, $templates);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get template for specific event and channel
|
||||
*
|
||||
@@ -40,25 +43,26 @@ class TemplateProvider {
|
||||
* @param string $recipient_type Recipient type ('customer' or 'staff')
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get_template($event_id, $channel_id, $recipient_type = 'customer') {
|
||||
public static function get_template($event_id, $channel_id, $recipient_type = 'customer')
|
||||
{
|
||||
$templates = self::get_templates();
|
||||
|
||||
|
||||
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
|
||||
|
||||
|
||||
if (isset($templates[$key])) {
|
||||
return $templates[$key];
|
||||
}
|
||||
|
||||
|
||||
// Return default if exists
|
||||
$defaults = self::get_default_templates();
|
||||
|
||||
|
||||
if (isset($defaults[$key])) {
|
||||
return $defaults[$key];
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save template
|
||||
*
|
||||
@@ -68,11 +72,12 @@ class TemplateProvider {
|
||||
* @param string $recipient_type Recipient type ('customer' or 'staff')
|
||||
* @return bool
|
||||
*/
|
||||
public static function save_template($event_id, $channel_id, $template, $recipient_type = 'customer') {
|
||||
public static function save_template($event_id, $channel_id, $template, $recipient_type = 'customer')
|
||||
{
|
||||
$templates = get_option(self::OPTION_KEY, []);
|
||||
|
||||
|
||||
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
|
||||
|
||||
|
||||
$templates[$key] = [
|
||||
'event_id' => $event_id,
|
||||
'channel_id' => $channel_id,
|
||||
@@ -82,10 +87,10 @@ class TemplateProvider {
|
||||
'variables' => $template['variables'] ?? [],
|
||||
'updated_at' => current_time('mysql'),
|
||||
];
|
||||
|
||||
|
||||
return update_option(self::OPTION_KEY, $templates);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete template (revert to default)
|
||||
*
|
||||
@@ -94,46 +99,48 @@ class TemplateProvider {
|
||||
* @param string $recipient_type Recipient type ('customer' or 'staff')
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete_template($event_id, $channel_id, $recipient_type = 'customer') {
|
||||
public static function delete_template($event_id, $channel_id, $recipient_type = 'customer')
|
||||
{
|
||||
$templates = get_option(self::OPTION_KEY, []);
|
||||
|
||||
|
||||
$key = "{$recipient_type}_{$event_id}_{$channel_id}";
|
||||
|
||||
|
||||
if (isset($templates[$key])) {
|
||||
unset($templates[$key]);
|
||||
return update_option(self::OPTION_KEY, $templates);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get default templates
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_default_templates() {
|
||||
public static function get_default_templates()
|
||||
{
|
||||
$templates = [];
|
||||
|
||||
|
||||
// Get all events from EventRegistry (single source of truth)
|
||||
$all_events = EventRegistry::get_all_events();
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
// 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',
|
||||
@@ -143,7 +150,7 @@ class TemplateProvider {
|
||||
'variables' => self::get_variables_for_event($event_id),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// Add push notification templates
|
||||
$templates['staff_order_placed_push'] = [
|
||||
'event_id' => 'order_placed',
|
||||
@@ -217,42 +224,44 @@ class TemplateProvider {
|
||||
'body' => __('A note has been added to order #{order_number}', 'woonoow'),
|
||||
'variables' => self::get_order_variables(),
|
||||
];
|
||||
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get variables for a specific event
|
||||
*
|
||||
* @param string $event_id Event ID
|
||||
* @return array
|
||||
*/
|
||||
private static function get_variables_for_event($event_id) {
|
||||
private static function get_variables_for_event($event_id)
|
||||
{
|
||||
// Product events
|
||||
if (in_array($event_id, ['low_stock', 'out_of_stock'])) {
|
||||
return self::get_product_variables();
|
||||
}
|
||||
|
||||
|
||||
// Customer events (but not order-related)
|
||||
if ($event_id === 'new_customer') {
|
||||
return self::get_customer_variables();
|
||||
}
|
||||
|
||||
|
||||
// Subscription events
|
||||
if (strpos($event_id, 'subscription_') === 0) {
|
||||
return self::get_subscription_variables();
|
||||
}
|
||||
|
||||
|
||||
// All other events are order-related
|
||||
return self::get_order_variables();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get available order variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_order_variables() {
|
||||
public static function get_order_variables()
|
||||
{
|
||||
return [
|
||||
'order_number' => __('Order Number', 'woonoow'),
|
||||
'order_total' => __('Order Total', 'woonoow'),
|
||||
@@ -272,49 +281,52 @@ class TemplateProvider {
|
||||
'billing_address' => __('Billing Address', 'woonoow'),
|
||||
'shipping_address' => __('Shipping Address', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'site_url' => __('Site URL', 'woonoow'),
|
||||
'store_email' => __('Store Email', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get available product variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_product_variables() {
|
||||
public static function get_product_variables()
|
||||
{
|
||||
return [
|
||||
'product_name' => __('Product Name', 'woonoow'),
|
||||
'product_sku' => __('Product SKU', 'woonoow'),
|
||||
'product_url' => __('Product URL', 'woonoow'),
|
||||
'stock_quantity' => __('Stock Quantity', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'site_url' => __('Site URL', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get available customer variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_customer_variables() {
|
||||
public static function get_customer_variables()
|
||||
{
|
||||
return [
|
||||
'customer_name' => __('Customer Name', 'woonoow'),
|
||||
'customer_email' => __('Customer Email', 'woonoow'),
|
||||
'customer_phone' => __('Customer Phone', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'site_url' => __('Site URL', 'woonoow'),
|
||||
'store_email' => __('Store Email', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get available subscription variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_subscription_variables() {
|
||||
public static function get_subscription_variables()
|
||||
{
|
||||
return [
|
||||
'subscription_id' => __('Subscription ID', 'woonoow'),
|
||||
'subscription_status' => __('Subscription Status', 'woonoow'),
|
||||
@@ -327,11 +339,11 @@ class TemplateProvider {
|
||||
'customer_name' => __('Customer Name', 'woonoow'),
|
||||
'customer_email' => __('Customer Email', 'woonoow'),
|
||||
'store_name' => __('Store Name', 'woonoow'),
|
||||
'store_url' => __('Store URL', 'woonoow'),
|
||||
'site_url' => __('Site URL', 'woonoow'),
|
||||
'my_account_url' => __('My Account URL', 'woonoow'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace variables in template
|
||||
*
|
||||
@@ -339,11 +351,12 @@ class TemplateProvider {
|
||||
* @param array $data Data to replace variables
|
||||
* @return string
|
||||
*/
|
||||
public static function replace_variables($content, $data) {
|
||||
public static function replace_variables($content, $data)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
$content = str_replace('{' . $key . '}', $value, $content);
|
||||
}
|
||||
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user