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:
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user