feat: complete Newsletter Campaigns Phase 1

- Add default campaign email template to DefaultTemplates.php
- Add toggle settings (campaign_scheduling, subscriber_limit_enabled)
- Add public unsubscribe endpoint with secure token verification
- Update CampaignManager to use NewsletterController unsubscribe URLs
- Add generate_unsubscribe_url() helper for email templates
This commit is contained in:
Dwindi Ramadhana
2025-12-31 21:17:59 +07:00
parent 3d5191aab3
commit d7505252ac
4 changed files with 138 additions and 6 deletions

View File

@@ -56,6 +56,23 @@ class NewsletterController {
return current_user_can('manage_options'); return current_user_can('manage_options');
}, },
]); ]);
// Public unsubscribe endpoint (no auth needed, uses token)
register_rest_route(self::API_NAMESPACE, '/newsletter/unsubscribe', [
'methods' => 'GET',
'callback' => [__CLASS__, 'unsubscribe'],
'permission_callback' => '__return_true',
'args' => [
'email' => [
'required' => true,
'type' => 'string',
],
'token' => [
'required' => true,
'type' => 'string',
],
],
]);
} }
public static function get_template(WP_REST_Request $request) { public static function get_template(WP_REST_Request $request) {
@@ -197,4 +214,78 @@ class NewsletterController {
], ],
], 200); ], 200);
} }
/**
* Handle unsubscribe request
*/
public static function unsubscribe(WP_REST_Request $request) {
$email = sanitize_email(urldecode($request->get_param('email')));
$token = sanitize_text_field($request->get_param('token'));
// Verify token
$expected_token = self::generate_unsubscribe_token($email);
if (!hash_equals($expected_token, $token)) {
return new WP_REST_Response([
'success' => false,
'message' => __('Invalid unsubscribe link', 'woonoow'),
], 400);
} }
// Get subscribers
$subscribers = get_option('woonoow_newsletter_subscribers', []);
$found = false;
foreach ($subscribers as &$sub) {
if (isset($sub['email']) && $sub['email'] === $email) {
$sub['status'] = 'unsubscribed';
$sub['unsubscribed_at'] = current_time('mysql');
$found = true;
break;
}
}
if (!$found) {
return new WP_REST_Response([
'success' => false,
'message' => __('Email not found', 'woonoow'),
], 404);
}
update_option('woonoow_newsletter_subscribers', $subscribers);
do_action('woonoow_newsletter_unsubscribed', $email);
// Return HTML page for nice UX
$site_name = get_bloginfo('name');
$html = sprintf(
'<!DOCTYPE html><html><head><title>%s</title><style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f5f5f5;}.box{background:white;padding:40px;border-radius:8px;text-align:center;box-shadow:0 2px 10px rgba(0,0,0,0.1);max-width:400px;}h1{color:#333;margin-bottom:16px;}p{color:#666;}</style></head><body><div class="box"><h1>✓ Unsubscribed</h1><p>You have been unsubscribed from %s newsletter.</p></div></body></html>',
__('Unsubscribed', 'woonoow'),
esc_html($site_name)
);
header('Content-Type: text/html; charset=utf-8');
echo $html;
exit;
}
/**
* Generate secure unsubscribe token
*/
private static function generate_unsubscribe_token($email) {
$secret = wp_salt('auth');
return hash_hmac('sha256', $email, $secret);
}
/**
* Generate unsubscribe URL for email templates
*/
public static function generate_unsubscribe_url($email) {
$token = self::generate_unsubscribe_token($email);
$base_url = rest_url('woonoow/v1/newsletter/unsubscribe');
return add_query_arg([
'email' => urlencode($email),
'token' => $token,
], $base_url);
}
}

View File

@@ -428,12 +428,8 @@ class CampaignManager {
* @return string * @return string
*/ */
private static function get_unsubscribe_url($email) { private static function get_unsubscribe_url($email) {
$token = wp_hash($email . 'woonoow_unsubscribe'); // Use NewsletterController's secure token-based URL
return add_query_arg([ return \WooNooW\API\NewsletterController::generate_unsubscribe_url($email);
'woonoow_unsubscribe' => 1,
'email' => urlencode($email),
'token' => $token,
], home_url());
} }
/** /**

View File

@@ -90,6 +90,7 @@ class DefaultTemplates
'order_cancelled' => self::customer_order_cancelled(), 'order_cancelled' => self::customer_order_cancelled(),
'order_refunded' => self::customer_order_refunded(), 'order_refunded' => self::customer_order_refunded(),
'new_customer' => self::customer_new_customer(), 'new_customer' => self::customer_new_customer(),
'newsletter_campaign' => self::customer_newsletter_campaign(),
], ],
'staff' => [ 'staff' => [
'order_placed' => self::staff_order_placed(), 'order_placed' => self::staff_order_placed(),
@@ -139,6 +140,7 @@ class DefaultTemplates
'order_cancelled' => 'Order #{order_number} has been cancelled', 'order_cancelled' => 'Order #{order_number} has been cancelled',
'order_refunded' => 'Refund processed for order #{order_number}', 'order_refunded' => 'Refund processed for order #{order_number}',
'new_customer' => 'Welcome to {site_name}! 🎁 Exclusive offer inside', 'new_customer' => 'Welcome to {site_name}! 🎁 Exclusive offer inside',
'newsletter_campaign' => '{campaign_title}',
], ],
'staff' => [ 'staff' => [
'order_placed' => '[NEW ORDER] #{order_number} - ${order_total} from {customer_name}', 'order_placed' => '[NEW ORDER] #{order_number} - ${order_total} from {customer_name}',
@@ -206,6 +208,30 @@ Got questions? Our customer service team is ready to help: {support_email}
[/card]'; [/card]';
} }
/**
* Customer: Newsletter Campaign
* Master design template for newsletter campaigns
* The {content} variable is replaced with the actual campaign content
*/
private static function customer_newsletter_campaign()
{
return '[card type="hero"]
## {campaign_title}
[/card]
[card]
{content}
[/card]
[card type="basic" bg="#f5f5f5"]
You are receiving this because you subscribed to {site_name} newsletter.
[Unsubscribe]({unsubscribe_url}) | [Visit Store]({site_url})
© {current_year} {site_name}. All rights reserved.
[/card]';
}
/** /**
* Customer: Order Placed * Customer: Order Placed
* Sent immediately when customer places an order * Sent immediately when customer places an order

View File

@@ -75,6 +75,25 @@ class NewsletterSettings {
'placeholder' => __('I agree to receive marketing emails', 'woonoow'), 'placeholder' => __('I agree to receive marketing emails', 'woonoow'),
'default' => __('I agree to receive marketing emails and understand I can unsubscribe at any time.', 'woonoow'), 'default' => __('I agree to receive marketing emails and understand I can unsubscribe at any time.', 'woonoow'),
], ],
// Campaign Settings
'campaign_scheduling' => [
'type' => 'toggle',
'label' => __('Campaign Scheduling', 'woonoow'),
'description' => __('Enable scheduled campaigns. When on, you can schedule campaigns to be sent at a specific date and time.', 'woonoow'),
'default' => false,
],
'subscriber_limit_enabled' => [
'type' => 'toggle',
'label' => __('Subscriber Limit', 'woonoow'),
'description' => __('Limit subscribers to 1000. When disabled, a custom database table will be created for unlimited subscribers.', 'woonoow'),
'default' => true,
],
'subscriber_limit' => [
'type' => 'number',
'label' => __('Max Subscribers', 'woonoow'),
'description' => __('Maximum number of subscribers when limit is enabled (default: 1000)', 'woonoow'),
'default' => 1000,
],
]; ];
return $schemas; return $schemas;