feat: add Newsletter Campaigns backend infrastructure
- Add CampaignManager.php with CPT registration, CRUD, batch sending - Add CampaignsController.php with 8 REST endpoints (list, create, get, update, delete, send, test, preview) - Register newsletter_campaign event in EventRegistry for email template - Initialize CampaignManager in Bootstrap.php - Register routes in Routes.php
This commit is contained in:
320
includes/Api/CampaignsController.php
Normal file
320
includes/Api/CampaignsController.php
Normal file
@@ -0,0 +1,320 @@
|
||||
<?php
|
||||
/**
|
||||
* Campaigns REST Controller
|
||||
*
|
||||
* REST API endpoints for newsletter campaigns
|
||||
*
|
||||
* @package WooNooW\API
|
||||
*/
|
||||
|
||||
namespace WooNooW\API;
|
||||
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
use WP_Error;
|
||||
use WooNooW\Core\Campaigns\CampaignManager;
|
||||
|
||||
class CampaignsController {
|
||||
|
||||
const API_NAMESPACE = 'woonoow/v1';
|
||||
|
||||
/**
|
||||
* Register REST routes
|
||||
*/
|
||||
public static function register_routes() {
|
||||
// List campaigns
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_campaigns'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Create campaign
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'create_campaign'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Get single campaign
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns/(?P<id>\d+)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'get_campaign'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Update campaign
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns/(?P<id>\d+)', [
|
||||
'methods' => 'PUT',
|
||||
'callback' => [__CLASS__, 'update_campaign'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Delete campaign
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns/(?P<id>\d+)', [
|
||||
'methods' => 'DELETE',
|
||||
'callback' => [__CLASS__, 'delete_campaign'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Send campaign
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns/(?P<id>\d+)/send', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'send_campaign'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Send test email
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns/(?P<id>\d+)/test', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [__CLASS__, 'send_test_email'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
|
||||
// Preview campaign
|
||||
register_rest_route(self::API_NAMESPACE, '/campaigns/(?P<id>\d+)/preview', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [__CLASS__, 'preview_campaign'],
|
||||
'permission_callback' => [__CLASS__, 'check_admin_permission'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check admin permission
|
||||
*/
|
||||
public static function check_admin_permission() {
|
||||
return current_user_can('manage_options');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all campaigns
|
||||
*/
|
||||
public static function get_campaigns(WP_REST_Request $request) {
|
||||
$campaigns = CampaignManager::get_all();
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => $campaigns,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create campaign
|
||||
*/
|
||||
public static function create_campaign(WP_REST_Request $request) {
|
||||
$data = [
|
||||
'title' => $request->get_param('title'),
|
||||
'subject' => $request->get_param('subject'),
|
||||
'content' => $request->get_param('content'),
|
||||
'status' => $request->get_param('status') ?: 'draft',
|
||||
'scheduled_at' => $request->get_param('scheduled_at'),
|
||||
];
|
||||
|
||||
$campaign_id = CampaignManager::create($data);
|
||||
|
||||
if (is_wp_error($campaign_id)) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => $campaign_id->get_error_message(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$campaign = CampaignManager::get($campaign_id);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => $campaign,
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single campaign
|
||||
*/
|
||||
public static function get_campaign(WP_REST_Request $request) {
|
||||
$campaign_id = (int) $request->get_param('id');
|
||||
$campaign = CampaignManager::get($campaign_id);
|
||||
|
||||
if (!$campaign) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => __('Campaign not found', 'woonoow'),
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => $campaign,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update campaign
|
||||
*/
|
||||
public static function update_campaign(WP_REST_Request $request) {
|
||||
$campaign_id = (int) $request->get_param('id');
|
||||
|
||||
$data = [];
|
||||
|
||||
if ($request->has_param('title')) {
|
||||
$data['title'] = $request->get_param('title');
|
||||
}
|
||||
if ($request->has_param('subject')) {
|
||||
$data['subject'] = $request->get_param('subject');
|
||||
}
|
||||
if ($request->has_param('content')) {
|
||||
$data['content'] = $request->get_param('content');
|
||||
}
|
||||
if ($request->has_param('status')) {
|
||||
$data['status'] = $request->get_param('status');
|
||||
}
|
||||
if ($request->has_param('scheduled_at')) {
|
||||
$data['scheduled_at'] = $request->get_param('scheduled_at');
|
||||
}
|
||||
|
||||
$result = CampaignManager::update($campaign_id, $data);
|
||||
|
||||
if (is_wp_error($result)) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => $result->get_error_message(),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$campaign = CampaignManager::get($campaign_id);
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'data' => $campaign,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete campaign
|
||||
*/
|
||||
public static function delete_campaign(WP_REST_Request $request) {
|
||||
$campaign_id = (int) $request->get_param('id');
|
||||
|
||||
$result = CampaignManager::delete($campaign_id);
|
||||
|
||||
if (!$result) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => __('Failed to delete campaign', 'woonoow'),
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => __('Campaign deleted', 'woonoow'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send campaign
|
||||
*/
|
||||
public static function send_campaign(WP_REST_Request $request) {
|
||||
$campaign_id = (int) $request->get_param('id');
|
||||
|
||||
$result = CampaignManager::send($campaign_id);
|
||||
|
||||
if (!$result['success']) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => $result['error'],
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => sprintf(
|
||||
__('Campaign sent to %d recipients (%d failed)', 'woonoow'),
|
||||
$result['sent'],
|
||||
$result['failed']
|
||||
),
|
||||
'sent' => $result['sent'],
|
||||
'failed' => $result['failed'],
|
||||
'total' => $result['total'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email
|
||||
*/
|
||||
public static function send_test_email(WP_REST_Request $request) {
|
||||
$campaign_id = (int) $request->get_param('id');
|
||||
$email = sanitize_email($request->get_param('email'));
|
||||
|
||||
if (!is_email($email)) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => __('Invalid email address', 'woonoow'),
|
||||
], 400);
|
||||
}
|
||||
|
||||
$result = CampaignManager::send_test($campaign_id, $email);
|
||||
|
||||
if (!$result) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => __('Failed to send test email', 'woonoow'),
|
||||
], 400);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'message' => sprintf(__('Test email sent to %s', 'woonoow'), $email),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview campaign
|
||||
*/
|
||||
public static function preview_campaign(WP_REST_Request $request) {
|
||||
$campaign_id = (int) $request->get_param('id');
|
||||
$campaign = CampaignManager::get($campaign_id);
|
||||
|
||||
if (!$campaign) {
|
||||
return new WP_REST_Response([
|
||||
'success' => false,
|
||||
'error' => __('Campaign not found', 'woonoow'),
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Use reflection to call private render method or make it public
|
||||
// For now, return a simple preview
|
||||
$renderer = \WooNooW\Core\Notifications\EmailRenderer::instance();
|
||||
$template = $renderer->get_template_settings('newsletter_campaign', 'customer');
|
||||
|
||||
$content = $campaign['content'];
|
||||
$subject = $campaign['subject'] ?: $campaign['title'];
|
||||
|
||||
if ($template) {
|
||||
$content = str_replace('{content}', $campaign['content'], $template['body']);
|
||||
$content = str_replace('{campaign_title}', $campaign['title'], $content);
|
||||
}
|
||||
|
||||
// Replace placeholders
|
||||
$site_name = get_bloginfo('name');
|
||||
$content = str_replace(['{site_name}', '{store_name}'], $site_name, $content);
|
||||
$content = str_replace('{site_url}', home_url(), $content);
|
||||
$content = str_replace('{subscriber_email}', 'subscriber@example.com', $content);
|
||||
$content = str_replace('{unsubscribe_url}', '#unsubscribe', $content);
|
||||
$content = str_replace('{current_date}', date_i18n(get_option('date_format')), $content);
|
||||
$content = str_replace('{current_year}', date('Y'), $content);
|
||||
|
||||
// Render with design template
|
||||
$design_path = $renderer->get_design_template();
|
||||
if (file_exists($design_path)) {
|
||||
$content = $renderer->render_html($design_path, $content, $subject, [
|
||||
'site_name' => $site_name,
|
||||
'site_url' => home_url(),
|
||||
]);
|
||||
}
|
||||
|
||||
return new WP_REST_Response([
|
||||
'success' => true,
|
||||
'subject' => $subject,
|
||||
'html' => $content,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ use WooNooW\Api\CustomersController;
|
||||
use WooNooW\Api\NewsletterController;
|
||||
use WooNooW\Api\ModulesController;
|
||||
use WooNooW\Api\ModuleSettingsController;
|
||||
use WooNooW\Api\CampaignsController;
|
||||
use WooNooW\Frontend\ShopController;
|
||||
use WooNooW\Frontend\CartController as FrontendCartController;
|
||||
use WooNooW\Frontend\AccountController;
|
||||
@@ -125,6 +126,9 @@ class Routes {
|
||||
// Newsletter controller
|
||||
NewsletterController::register_routes();
|
||||
|
||||
// Campaigns controller
|
||||
CampaignsController::register_routes();
|
||||
|
||||
// Modules controller
|
||||
$modules_controller = new ModulesController();
|
||||
$modules_controller->register_routes();
|
||||
|
||||
@@ -22,6 +22,7 @@ use WooNooW\Core\DataStores\OrderStore;
|
||||
use WooNooW\Core\MediaUpload;
|
||||
use WooNooW\Core\Notifications\PushNotificationHandler;
|
||||
use WooNooW\Core\Notifications\EmailManager;
|
||||
use WooNooW\Core\Campaigns\CampaignManager;
|
||||
use WooNooW\Core\ActivityLog\ActivityLogTable;
|
||||
use WooNooW\Branding;
|
||||
use WooNooW\Frontend\Assets as FrontendAssets;
|
||||
@@ -40,6 +41,7 @@ class Bootstrap {
|
||||
MediaUpload::init();
|
||||
PushNotificationHandler::init();
|
||||
EmailManager::instance(); // Initialize custom email system
|
||||
CampaignManager::init(); // Initialize campaigns CPT
|
||||
|
||||
// Frontend (customer-spa)
|
||||
FrontendAssets::init();
|
||||
|
||||
483
includes/Core/Campaigns/CampaignManager.php
Normal file
483
includes/Core/Campaigns/CampaignManager.php
Normal file
@@ -0,0 +1,483 @@
|
||||
<?php
|
||||
/**
|
||||
* Campaign Manager
|
||||
*
|
||||
* Manages newsletter campaign CRUD operations and sending
|
||||
*
|
||||
* @package WooNooW\Core\Campaigns
|
||||
*/
|
||||
|
||||
namespace WooNooW\Core\Campaigns;
|
||||
|
||||
if (!defined('ABSPATH')) exit;
|
||||
|
||||
class CampaignManager {
|
||||
|
||||
const POST_TYPE = 'wnw_campaign';
|
||||
const CRON_HOOK = 'woonoow_process_scheduled_campaigns';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*/
|
||||
public static function instance() {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
public static function init() {
|
||||
add_action('init', [__CLASS__, 'register_post_type']);
|
||||
add_action(self::CRON_HOOK, [__CLASS__, 'process_scheduled_campaigns']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register campaign post type
|
||||
*/
|
||||
public static function register_post_type() {
|
||||
register_post_type(self::POST_TYPE, [
|
||||
'labels' => [
|
||||
'name' => __('Campaigns', 'woonoow'),
|
||||
'singular_name' => __('Campaign', 'woonoow'),
|
||||
],
|
||||
'public' => false,
|
||||
'show_ui' => false,
|
||||
'show_in_rest' => false,
|
||||
'supports' => ['title'],
|
||||
'capability_type' => 'post',
|
||||
'map_meta_cap' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new campaign
|
||||
*
|
||||
* @param array $data Campaign data
|
||||
* @return int|WP_Error Campaign ID or error
|
||||
*/
|
||||
public static function create($data) {
|
||||
$post_data = [
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => 'publish',
|
||||
'post_title' => sanitize_text_field($data['title'] ?? 'Untitled Campaign'),
|
||||
];
|
||||
|
||||
$campaign_id = wp_insert_post($post_data, true);
|
||||
|
||||
if (is_wp_error($campaign_id)) {
|
||||
return $campaign_id;
|
||||
}
|
||||
|
||||
// Save meta fields
|
||||
self::update_meta($campaign_id, $data);
|
||||
|
||||
return $campaign_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update campaign
|
||||
*
|
||||
* @param int $campaign_id Campaign ID
|
||||
* @param array $data Campaign data
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public static function update($campaign_id, $data) {
|
||||
$post = get_post($campaign_id);
|
||||
|
||||
if (!$post || $post->post_type !== self::POST_TYPE) {
|
||||
return new \WP_Error('invalid_campaign', __('Campaign not found', 'woonoow'));
|
||||
}
|
||||
|
||||
// Update title if provided
|
||||
if (isset($data['title'])) {
|
||||
wp_update_post([
|
||||
'ID' => $campaign_id,
|
||||
'post_title' => sanitize_text_field($data['title']),
|
||||
]);
|
||||
}
|
||||
|
||||
// Update meta fields
|
||||
self::update_meta($campaign_id, $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update campaign meta
|
||||
*
|
||||
* @param int $campaign_id
|
||||
* @param array $data
|
||||
*/
|
||||
private static function update_meta($campaign_id, $data) {
|
||||
$meta_fields = [
|
||||
'subject' => '_wnw_subject',
|
||||
'content' => '_wnw_content',
|
||||
'status' => '_wnw_status',
|
||||
'scheduled_at' => '_wnw_scheduled_at',
|
||||
];
|
||||
|
||||
foreach ($meta_fields as $key => $meta_key) {
|
||||
if (isset($data[$key])) {
|
||||
$value = $data[$key];
|
||||
|
||||
// Sanitize based on field type
|
||||
if ($key === 'content') {
|
||||
$value = wp_kses_post($value);
|
||||
} elseif ($key === 'scheduled_at') {
|
||||
$value = sanitize_text_field($value);
|
||||
} elseif ($key === 'status') {
|
||||
$allowed = ['draft', 'scheduled', 'sending', 'sent', 'failed'];
|
||||
$value = in_array($value, $allowed) ? $value : 'draft';
|
||||
} else {
|
||||
$value = sanitize_text_field($value);
|
||||
}
|
||||
|
||||
update_post_meta($campaign_id, $meta_key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// Set default status if not provided
|
||||
if (!get_post_meta($campaign_id, '_wnw_status', true)) {
|
||||
update_post_meta($campaign_id, '_wnw_status', 'draft');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get campaign by ID
|
||||
*
|
||||
* @param int $campaign_id
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get($campaign_id) {
|
||||
$post = get_post($campaign_id);
|
||||
|
||||
if (!$post || $post->post_type !== self::POST_TYPE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::format_campaign($post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all campaigns
|
||||
*
|
||||
* @param array $args Query args
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all($args = []) {
|
||||
$defaults = [
|
||||
'post_type' => self::POST_TYPE,
|
||||
'post_status' => 'any',
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
|
||||
$query_args = wp_parse_args($args, $defaults);
|
||||
$query_args['post_type'] = self::POST_TYPE; // Force post type
|
||||
|
||||
$posts = get_posts($query_args);
|
||||
|
||||
return array_map([__CLASS__, 'format_campaign'], $posts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format campaign post to array
|
||||
*
|
||||
* @param WP_Post $post
|
||||
* @return array
|
||||
*/
|
||||
private static function format_campaign($post) {
|
||||
return [
|
||||
'id' => $post->ID,
|
||||
'title' => $post->post_title,
|
||||
'subject' => get_post_meta($post->ID, '_wnw_subject', true),
|
||||
'content' => get_post_meta($post->ID, '_wnw_content', true),
|
||||
'status' => get_post_meta($post->ID, '_wnw_status', true) ?: 'draft',
|
||||
'scheduled_at' => get_post_meta($post->ID, '_wnw_scheduled_at', true),
|
||||
'sent_at' => get_post_meta($post->ID, '_wnw_sent_at', true),
|
||||
'recipient_count' => (int) get_post_meta($post->ID, '_wnw_recipient_count', true),
|
||||
'sent_count' => (int) get_post_meta($post->ID, '_wnw_sent_count', true),
|
||||
'failed_count' => (int) get_post_meta($post->ID, '_wnw_failed_count', true),
|
||||
'created_at' => $post->post_date,
|
||||
'updated_at' => $post->post_modified,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete campaign
|
||||
*
|
||||
* @param int $campaign_id
|
||||
* @return bool
|
||||
*/
|
||||
public static function delete($campaign_id) {
|
||||
$post = get_post($campaign_id);
|
||||
|
||||
if (!$post || $post->post_type !== self::POST_TYPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return wp_delete_post($campaign_id, true) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send campaign
|
||||
*
|
||||
* @param int $campaign_id
|
||||
* @return array Result with sent/failed counts
|
||||
*/
|
||||
public static function send($campaign_id) {
|
||||
$campaign = self::get($campaign_id);
|
||||
|
||||
if (!$campaign) {
|
||||
return ['success' => false, 'error' => __('Campaign not found', 'woonoow')];
|
||||
}
|
||||
|
||||
if ($campaign['status'] === 'sent') {
|
||||
return ['success' => false, 'error' => __('Campaign already sent', 'woonoow')];
|
||||
}
|
||||
|
||||
// Get subscribers
|
||||
$subscribers = self::get_subscribers();
|
||||
|
||||
if (empty($subscribers)) {
|
||||
return ['success' => false, 'error' => __('No subscribers to send to', 'woonoow')];
|
||||
}
|
||||
|
||||
// Update status to sending
|
||||
update_post_meta($campaign_id, '_wnw_status', 'sending');
|
||||
update_post_meta($campaign_id, '_wnw_recipient_count', count($subscribers));
|
||||
|
||||
$sent = 0;
|
||||
$failed = 0;
|
||||
|
||||
// Get email template
|
||||
$template = self::render_campaign_email($campaign);
|
||||
|
||||
// Send in batches
|
||||
$batch_size = 50;
|
||||
$batches = array_chunk($subscribers, $batch_size);
|
||||
|
||||
foreach ($batches as $batch) {
|
||||
foreach ($batch as $subscriber) {
|
||||
$email = $subscriber['email'];
|
||||
|
||||
// Replace subscriber-specific variables
|
||||
$body = str_replace('{subscriber_email}', $email, $template['body']);
|
||||
$body = str_replace('{unsubscribe_url}', self::get_unsubscribe_url($email), $body);
|
||||
|
||||
// Send email
|
||||
$result = wp_mail(
|
||||
$email,
|
||||
$template['subject'],
|
||||
$body,
|
||||
['Content-Type: text/html; charset=UTF-8']
|
||||
);
|
||||
|
||||
if ($result) {
|
||||
$sent++;
|
||||
} else {
|
||||
$failed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Small delay between batches
|
||||
if (count($batches) > 1) {
|
||||
sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
// Update campaign stats
|
||||
update_post_meta($campaign_id, '_wnw_sent_count', $sent);
|
||||
update_post_meta($campaign_id, '_wnw_failed_count', $failed);
|
||||
update_post_meta($campaign_id, '_wnw_sent_at', current_time('mysql'));
|
||||
update_post_meta($campaign_id, '_wnw_status', $failed > 0 && $sent === 0 ? 'failed' : 'sent');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'sent' => $sent,
|
||||
'failed' => $failed,
|
||||
'total' => count($subscribers),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email
|
||||
*
|
||||
* @param int $campaign_id
|
||||
* @param string $email Test email address
|
||||
* @return bool
|
||||
*/
|
||||
public static function send_test($campaign_id, $email) {
|
||||
$campaign = self::get($campaign_id);
|
||||
|
||||
if (!$campaign) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$template = self::render_campaign_email($campaign);
|
||||
|
||||
// Replace subscriber-specific variables
|
||||
$body = str_replace('{subscriber_email}', $email, $template['body']);
|
||||
$body = str_replace('{unsubscribe_url}', '#', $body);
|
||||
|
||||
return wp_mail(
|
||||
$email,
|
||||
'[TEST] ' . $template['subject'],
|
||||
$body,
|
||||
['Content-Type: text/html; charset=UTF-8']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render campaign email using EmailRenderer
|
||||
*
|
||||
* @param array $campaign
|
||||
* @return array ['subject' => string, 'body' => string]
|
||||
*/
|
||||
private static function render_campaign_email($campaign) {
|
||||
$renderer = \WooNooW\Core\Notifications\EmailRenderer::instance();
|
||||
|
||||
// Get the campaign email template
|
||||
$template = $renderer->get_template_settings('newsletter_campaign', 'customer');
|
||||
|
||||
// Fallback if no template configured
|
||||
if (!$template) {
|
||||
$subject = $campaign['subject'] ?: $campaign['title'];
|
||||
$body = $campaign['content'];
|
||||
} else {
|
||||
$subject = $template['subject'] ?: $campaign['subject'];
|
||||
|
||||
// Replace {content} with campaign content
|
||||
$body = str_replace('{content}', $campaign['content'], $template['body']);
|
||||
|
||||
// Replace {campaign_title}
|
||||
$body = str_replace('{campaign_title}', $campaign['title'], $body);
|
||||
}
|
||||
|
||||
// Replace common variables
|
||||
$site_name = get_bloginfo('name');
|
||||
$site_url = home_url();
|
||||
|
||||
$subject = str_replace(['{site_name}', '{store_name}'], $site_name, $subject);
|
||||
$body = str_replace(['{site_name}', '{store_name}'], $site_name, $body);
|
||||
$body = str_replace('{site_url}', $site_url, $body);
|
||||
$body = str_replace('{current_date}', date_i18n(get_option('date_format')), $body);
|
||||
$body = str_replace('{current_year}', date('Y'), $body);
|
||||
|
||||
// Render through email design template
|
||||
$design_path = $renderer->get_design_template();
|
||||
if (file_exists($design_path)) {
|
||||
$body = $renderer->render_html($design_path, $body, $subject, [
|
||||
'site_name' => $site_name,
|
||||
'site_url' => $site_url,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscribers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_subscribers() {
|
||||
// Check if using custom table
|
||||
$use_table = !get_option('woonoow_newsletter_limit_enabled', true);
|
||||
|
||||
if ($use_table && self::has_subscribers_table()) {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'woonoow_subscribers';
|
||||
return $wpdb->get_results(
|
||||
"SELECT email, user_id FROM {$table} WHERE status = 'active'",
|
||||
ARRAY_A
|
||||
);
|
||||
}
|
||||
|
||||
// Use wp_options storage
|
||||
$subscribers = get_option('woonoow_newsletter_subscribers', []);
|
||||
return array_filter($subscribers, function($sub) {
|
||||
return ($sub['status'] ?? 'active') === 'active';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if subscribers table exists
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function has_subscribers_table() {
|
||||
global $wpdb;
|
||||
$table = $wpdb->prefix . 'woonoow_subscribers';
|
||||
return $wpdb->get_var("SHOW TABLES LIKE '{$table}'") === $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unsubscribe URL
|
||||
*
|
||||
* @param string $email
|
||||
* @return string
|
||||
*/
|
||||
private static function get_unsubscribe_url($email) {
|
||||
$token = wp_hash($email . 'woonoow_unsubscribe');
|
||||
return add_query_arg([
|
||||
'woonoow_unsubscribe' => 1,
|
||||
'email' => urlencode($email),
|
||||
'token' => $token,
|
||||
], home_url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process scheduled campaigns (WP-Cron)
|
||||
*/
|
||||
public static function process_scheduled_campaigns() {
|
||||
// Only if scheduling is enabled
|
||||
if (!get_option('woonoow_campaign_scheduling_enabled', false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$campaigns = self::get_all([
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => '_wnw_status',
|
||||
'value' => 'scheduled',
|
||||
],
|
||||
[
|
||||
'key' => '_wnw_scheduled_at',
|
||||
'value' => current_time('mysql'),
|
||||
'compare' => '<=',
|
||||
'type' => 'DATETIME',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
foreach ($campaigns as $campaign) {
|
||||
self::send($campaign['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable scheduling (registers cron)
|
||||
*/
|
||||
public static function enable_scheduling() {
|
||||
if (!wp_next_scheduled(self::CRON_HOOK)) {
|
||||
wp_schedule_event(time(), 'hourly', self::CRON_HOOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable scheduling (clears cron)
|
||||
*/
|
||||
public static function disable_scheduling() {
|
||||
wp_clear_scheduled_hook(self::CRON_HOOK);
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,21 @@ class EventRegistry {
|
||||
'wc_email' => '',
|
||||
'enabled' => true,
|
||||
],
|
||||
'newsletter_campaign' => [
|
||||
'id' => 'newsletter_campaign',
|
||||
'label' => __('Newsletter Campaign', 'woonoow'),
|
||||
'description' => __('Master email design template for newsletter campaigns', 'woonoow'),
|
||||
'category' => 'marketing',
|
||||
'recipient_type' => 'customer',
|
||||
'wc_email' => '',
|
||||
'enabled' => true,
|
||||
'variables' => [
|
||||
'{content}' => __('Campaign content', 'woonoow'),
|
||||
'{campaign_title}' => __('Campaign title', 'woonoow'),
|
||||
'{subscriber_email}' => __('Subscriber email', 'woonoow'),
|
||||
'{unsubscribe_url}' => __('Unsubscribe link', 'woonoow'),
|
||||
],
|
||||
],
|
||||
|
||||
// ===== ORDER INITIATION =====
|
||||
'order_placed' => [
|
||||
|
||||
Reference in New Issue
Block a user