rl-warmup-plugin/includes/class-rl-mailwarmer-campaign-helper.php

965 lines
37 KiB
PHP

<?php
/**
* Helper class for managing Campaigns.
*/
class RL_MailWarmer_Campaign_Helper
{
/**
* Generate and save a unique 16-digit campaign tracking ID.
*
* @param int $campaign_id The ID of the campaign.
* @return void
* @throws Exception If unable to generate a unique ID.
*/
public static function generate_campaign_tracking_id($campaign_id)
{
$max_attempts = 10;
$attempts = 0;
while ($attempts < $max_attempts) {
$tracking_id = str_pad(mt_rand(0, 9999999999), 10, '0', STR_PAD_LEFT);
// Check if the ID is unique (not used by any campaign)
$query = new WP_Query([
'post_type' => 'campaign',
'meta_query' => [
[
'key' => 'campaign_tracking_id',
'value' => $tracking_id,
'compare' => '=',
],
],
'posts_per_page' => 1,
'fields' => 'ids'
]);
if (!$query->have_posts()) {
// The ID is unique, save it
log_to_file("generate_campaign_tracking_id - Campaign: {$campaign_id} ID: {$tracking_id}");
update_post_meta($campaign_id, 'campaign_tracking_id', $tracking_id);
return true;
}
$attempts++;
}
// If maximum attempts exceeded, throw an exception
log_to_file("generate_campaign_tracking_id - Unable to generate a unique campaign tracking ID!");
throw new Exception(__('Unable to generate a unique campaign tracking ID.', 'rl-mailwarmer'));
}
/**
* Calculate the campaign timeline with semi-randomized daily email goals.
*
* @param int $campaign_id The campaign post ID.
* @return array Weekly email schedule with randomized daily goals.
* @throws Exception If the campaign parameters are invalid.
*/
public static function calculate_campaign_timeline($campaign_id)
{
action_log("calculate_campaign_timeline - Creating timeline for campaign {$campaign_id}");
// Fetch campaign details
$warmup_period = (int) get_post_meta($campaign_id, 'warmup_period', true); // Weeks
$target_volume = (int) get_post_meta($campaign_id, 'target_volume', true); // Daily emails to ramp up to
$weekend_reduction_factor = get_field('weekend_reduction_factor', $campaign_id) ? (int) get_field('weekend_reduction_factor', $campaign_id) : 25;
// Make it so the volume is REDUCED by this percentage, not capped at it
$weekend_reduction_factor = 100 - $weekend_reduction_factor;
$start_date = get_post_meta($campaign_id, 'start_date', true); // Campaign start date
// log_to_file("calculate_campaign_timeline - Target weekend reduction factor: {$weekend_reduction_factor}");
if (!$warmup_period || !$target_volume || !$start_date) {
throw new Exception(__('Invalid campaign parameters.', 'rl-mailwarmer'));
}
// Fetch ACF options
$holidays_raw = get_field('calendar_holidays', 'option'); // Holidays in 12/25/25 format
$holidays = array_map(
fn($date) => DateTime::createFromFormat('m/d/y', trim($date))->format('Y-m-d'),
explode("\n", $holidays_raw)
);
$min_starting_volume = (int) get_field('min_starting_email_volume', 'option') ?: 5;
$max_daily_volume = (int) get_field('max_campaign_daily_email_volume', 'option') ?: 1000;
// Calculate starting daily volume (2.5% of target volume)
$starting_daily_volume = max(ceil($target_volume * 0.025), $min_starting_volume);
// Initialize variables
$timeline = [];
$total_days = $warmup_period * 7; // Total days in the campaign
$start_date = new DateTime($start_date);
// Calculate daily ramp-up rate
$base_daily_increase = ($target_volume - $starting_daily_volume) / ($total_days * 0.75);
log_to_file("calculate_campaign_timeline - Ramping up from $min_starting_volume to $target_volume over $total_days days, increasing by $base_daily_increase (+/- 25%) each day with no more than $max_daily_volume emails in a day");
// Generate timeline
for ($day = 0; $day < $total_days; $day++) {
$current_date = clone $start_date;
$current_date->modify("+{$day} days");
$date_formatted = $current_date->format('Y-m-d');
// Adjust for holidays and weekends
$is_weekend = in_array($current_date->format('N'), [6, 7]);
$is_holiday = in_array($date_formatted, $holidays);
$reduction_factor = ($is_weekend || $is_holiday) ? mt_rand($weekend_reduction_factor - 5, $weekend_reduction_factor + 5) / 100 : 1;
// log_to_file("calculate_campaign_timeline - Using daily reduction factor: {$reduction_factor}");
// Calculate daily volume
$percentage_variation = mt_rand(-25, 25);
$daily_increase = round($base_daily_increase + ($base_daily_increase * $percentage_variation / 100), 2);
$daily_volume = min(
ceil(($starting_daily_volume + ($daily_increase * $day)) * $reduction_factor),
ceil($target_volume + ($target_volume * mt_rand(5, 20)) / 100)
);
log_to_file("calculate_campaign_timeline - Daily increase {$daily_increase}");
if ($daily_volume > $max_daily_volume) {
// log_to_file("calculate_campaign_timeline - Max Daily Volume hit for campaign $campaign_id! Capping number of emails");
$daily_volume = $max_daily_volume;
}
// Calculate percent daily change
$percent_daily_change = $day > 0 ? (($daily_volume - $last_daily_volume) / $last_daily_volume) * 100 : 0;
// Round to 2 decimal places
$percent_daily_change = round($percent_daily_change, 2);
// Add a "+" sign if positive
$formatted_percent_change = $percent_daily_change > 0 ? '+' . $percent_daily_change : (string)$percent_daily_change;
$last_daily_volume = $daily_volume;
$timeline[$date_formatted] = [
'target_volume' => $daily_volume,
'current_volume' => 0,
'items_sent' => 0,
'percent_daily_change' => $formatted_percent_change,
'daily_increase' => $daily_increase,
];
// log_to_file("calculate_campaign_timeline - $day: $daily_volume");
}
// Save the updated campaign timeline
update_post_meta($campaign_id, 'campaign_timeline', json_encode($timeline, JSON_PRETTY_PRINT));
update_post_meta($campaign_id, 'campaign_timeline_json', $timeline);
if( self::fill_campaign_timeline($campaign_id, $timeline) )
{
// Check the number of saved messages per date
$message_counts = RL_MailWarmer_DB_Helper::get_message_counts_by_date($campaign_id);
update_post_meta($campaign_id, 'message_counts', json_encode($message_counts, JSON_PRETTY_PRINT));
action_log("calculate_campaign_timeline - Finished creating timeline for campaign {$campaign_id}");
return true;
} else {
log_to_file("calculate_campaign_timeline - Error filling campaign timeline!");
throw new Exception(__("Error filling campaign timeline for campaign ID: {$campaign_id}", 'rl-mailwarmer'));
}
}
/**
* Generate conversation placeholders of varying lengths and allocate them until all dates in the campaign meet the targets set by RL_MailWarmer_Campaign_Helper::calculate_campaign_timeline()
*
* @param int $campaign_id The campaign post ID.
* @return array Weekly email schedule with randomized daily goals.
* @throws Exception If the campaign parameters are invalid.
*/
public static function fill_campaign_timeline(int $campaign_id, $campaign_timeline = null) {
$filled_dates = [];
$weekly_volumes = [];
$current_week = '';
$weekly_total = 0;
$total_conversation_count = 0;
$total_message_count = 0;
$campaign_limited = get_post_meta($campaign_id, 'campaign_limited', true);
if (!$campaign_timeline) {
try {
$campaign_timeline = get_field('campaign_timeline', $campaign_id);
} catch (Exception $e) {
log_to_file("fill_campaign_timeline - No campaign timeline found for campaign ID: {$campaign_id}");
throw new Exception(__("No campaign timeline found for campaign ID: {$campaign_id}", 'rl-mailwarmer'));
}
}
if (!$campaign_limited ) {
$ratios = [
'extra-long' => [
'percent_of_volume_lower' => 1,
'percent_of_volume_upper' => 3,
'num_participants_lower' => 1,
'num_participants_upper' => 8,
'num_responses_lower' => 5,
'num_responses_upper' => 10
],
'long' => [
'percent_of_volume_lower' => 2,
'percent_of_volume_upper' => 4,
'num_participants_lower' => 1,
'num_participants_upper' => 6,
'num_responses_lower' => 4,
'num_responses_upper' => 6
],
'medium' => [
'percent_of_volume_lower' => 2,
'percent_of_volume_upper' => 6,
'num_participants_lower' => 1,
'num_participants_upper' => 5,
'num_responses_lower' => 3,
'num_responses_upper' => 5
],
'short' => [
'percent_of_volume_lower' => 15,
'percent_of_volume_upper' => 20,
'num_participants_lower' => 1,
'num_participants_upper' => 4,
'num_responses_lower' => 2,
'num_responses_upper' => 4
],
];
} else {
log_to_file("fill_campaign_timeline - Creating a limited campaign");
$ratios = [
'short' => [
'percent_of_volume_lower' => 100,
'percent_of_volume_upper' => 100,
'num_participants_lower' => 1,
'num_participants_upper' => 5,
'num_responses_lower' => 1,
'num_responses_upper' => 1
],
];
}
$warmup_period = (int) get_post_meta($campaign_id, 'warmup_period', true); // Weeks
$start_date = get_post_meta($campaign_id, 'start_date', true); // Campaign start date
$total_days = $warmup_period * 7; // Total days in the campaign
// $total_days = $warmup_period; // Total days in the campaign
$start_date = date('Y-m-d H:i:s', strtotime($start_date));
$end_date = date('Y-m-d 23:59:59', strtotime($start_date . " + {$total_days} days"));
// $end_date = date('Y-m-d H:i', strtotime($start_date . " +{$total_days} days"));
log_to_file("fill_campaign_timeline - Start: $start_date End: $end_date");
// log_to_file("fill_campaign_timeline - Campaign timeline $campaign_timeline");
// Calculate weekly volumes
foreach ($campaign_timeline as $date => $data) {
// log_to_file("fill_campaign_timeline - Campaign timeline date: {$date}", $data);
$timestamp = strtotime($date);
$year = date('o', $timestamp); // ISO year
$week = date('W', $timestamp); // ISO week
$week_key = sprintf('%d-%02d', $year, $week);
if (!isset($weekly_volumes[$week_key])) {
$weekly_volumes[$week_key] = 0;
}
// log_to_file("fill_campaign_timeline - Weekly Volume: ", $weekly_volumes);
$weekly_volumes[$week_key] += $data['target_volume'];
}
action_log("fill_campaign_timeline - Creating multi-step conversations for campaign {$campaign_id}. Message count: {$total_message_count}");
// Process each week
foreach ($weekly_volumes as $week => $volume) {
log_to_file("fill_campaign_timeline - Week $week goal: $volume");
$weekly_scheduled_messages = 0;
// Process each conversation length (short, medium, long, etc.)
foreach ($ratios as $length => $ratio) {
// Calculate number of conversations for this length
$percent = mt_rand($ratio['percent_of_volume_lower'], $ratio['percent_of_volume_upper']) / 100;
$num_conversations = ceil($volume * $percent);
log_to_file("fill_campaign_timeline - Generating $num_conversations $length conversations");
// Skip if no conversations to generate
if ($num_conversations === 0) continue;
// Generate conversations
for ($i = 0; $i < $num_conversations; $i++) {
// Check if weekly volume reached
if ($weekly_scheduled_messages >= $volume) {
break 2; // Break both loops
}
// Generate random number of responses for this conversation
$num_particpants = mt_rand($ratio['num_participants_lower'], $ratio['num_participants_upper']);
// Generate random number of responses for this conversation
$num_responses = mt_rand($ratio['num_responses_lower'], $ratio['num_responses_upper']);
// Get week start date
list($year, $weekNum) = explode('-', $week);
$week_start = date('Y-m-d', strtotime($year . 'W' . $weekNum));
if ($week_start < $start_date) {
$week_start = $start_date;
}
// Generate conversation blueprint
$conversation_steps = RL_MailWarmer_Conversation_Handler::generate_conversation_blueprint(
$campaign_id,
$num_particpants,
$num_responses,
$week_start,
$end_date,
$filled_dates
);
// Update timeline volumes
foreach ($conversation_steps as $step) {
$step_date = date('Y-m-d', strtotime($step['scheduled_for']));
if (isset($campaign_timeline[$step_date])) {
$campaign_timeline[$step_date]['current_volume']++;
$weekly_scheduled_messages++;
$total_message_count++;
// Check if date is now filled
if ($campaign_timeline[$step_date]['current_volume'] >=
$campaign_timeline[$step_date]['target_volume']) {
$filled_dates[] = $step_date;
}
}
}
$total_conversation_count++;
}
}
}
action_log("fill_campaign_timeline - Creating single-step conversations for campaign {$campaign_id}. Message count: {$total_message_count}");
// log_to_file("fill_campaign_timeline - Campaign Timeline without single-step conversations: ", $campaign_timeline);
// Fill remaining capacity with single-message conversations
log_to_file("fill_campaign_timeline - Filling remaining days with single conversations");
foreach ($campaign_timeline as $date => $data) {
$remaining = $data['target_volume'] - $data['current_volume'];
// log_to_file("fill_campaign_timeline - $date remaining: $remaining");
while ($remaining > 0) {
$conversation_steps = RL_MailWarmer_Conversation_Handler::generate_conversation_blueprint(
$campaign_id,
1,
1,
$date,
$date
);
foreach ($conversation_steps as $step) {
$step_date = date('Y-m-d', strtotime($step['scheduled_for']));
if (isset($campaign_timeline[$step_date])) {
$campaign_timeline[$step_date]['current_volume']++;
$total_message_count++;
$remaining--;
if ($campaign_timeline[$step_date]['current_volume'] >=
$campaign_timeline[$step_date]['target_volume']) {
$filled_dates[] = $step_date;
}
}
}
$total_conversation_count++;
}
}
action_log("fill_campaign_timeline - Created {$total_conversation_count} conversations with {$total_message_count} messages for campaign {$campaign_id}");
// return $campaign_timeline;
return true;
}
/**
* Parse the AI response into structured steps for message creation.
*
* @param string $ai_response The JSON string returned by ChatGPT.
* @return array An array of parsed conversation steps.
*/
// public static function parse_ai_response($ai_response) {
// $steps = json_decode($ai_response, true);
// if (json_last_error() !== JSON_ERROR_NONE) {
// return new WP_Error('invalid_response', __('Failed to parse AI response.', 'rl-mailwarmer'));
// }
// return $steps;
// }
}
/**
* Add a meta box for generating the campaign timeline.
*/
add_action('add_meta_boxes', function () {
add_meta_box(
'show_campaign_tracking_id',
__('Tracking ID', 'rl-mailwarmer'),
'rl_mailwarmer_render_tracking_id_box',
'campaign',
'side',
'default'
);
add_meta_box(
'generate_campaign_timeline',
__('Generate Timeline', 'rl-mailwarmer'),
'rl_mailwarmer_render_generate_timeline_box',
'campaign',
'side',
'default'
);
});
/**
* Render the "Tracking ID" meta box.
*
* @param WP_Post $post The current post object.
*/
function rl_mailwarmer_render_tracking_id_box($post)
{
$campaign_tracking_id = get_field('campaign_tracking_id', $post->ID);
?>
<span class="tracking_id"><?php echo $campaign_tracking_id; ?></span>
<?php
}
/**
* Render the "Generate Timeline" meta box.
*
* @param WP_Post $post The current post object.
*/
function rl_mailwarmer_render_generate_timeline_box($post)
{
// Add a nonce for security
wp_nonce_field('generate_timeline_nonce', 'generate_timeline_nonce_field');
// Render the form
?>
<form id="generate-timeline-form">
<p>
<button type="button" id="generate-timeline-button" class="button button-primary">
<?php esc_html_e('Generate Timeline', 'rl-mailwarmer'); ?>
</button>
</p>
<div id="generate-timeline-result"></div>
</form>
<?php
}
add_action('admin_enqueue_scripts', function ($hook) {
global $post;
// Ensure this is the Add/Edit Campaign screen
if (($hook === 'post.php' || $hook === 'post-new.php') && $post->post_type === 'campaign') {
wp_enqueue_script(
'rl-mailwarmer-generate-timeline-js',
RL_MAILWARMER_URL . 'js/generate-timeline.js', // Adjust path as needed
['jquery'],
null,
true
);
wp_localize_script('rl-mailwarmer-generate-timeline-js', 'rlMailWarmerGenerateTimeline', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('generate_timeline_nonce'),
]);
}
});
add_action('wp_ajax_rl_mailwarmer_generate_timeline', function () {
// Verify nonce
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'generate_timeline_nonce')) {
wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer'));
}
// Validate and sanitize input
$post_id = intval($_POST['post_id']);
if (!$post_id || get_post_type($post_id) !== 'campaign') {
wp_send_json_error(__('Invalid campaign ID.', 'rl-mailwarmer'));
}
// Generate the timeline
try {
RL_MailWarmer_Campaign_Helper::calculate_campaign_timeline($post_id);
// log_to_file("wp_ajax_rl_mailwarmer_generate_timeline - Generated Timeline: ", $timeline);
// update_post_meta($post_id, "campaign_timeline", $timeline);
wp_send_json_success("Timeline generated");
} catch (Exception $e) {
wp_send_json_error($e->getMessage());
}
});
/*
* Generate Conversation Metabox
*
*/
add_action('add_meta_boxes', function () {
add_meta_box(
'generate_conversation_box', // Unique ID for the meta box
__('Generate Conversation', 'rl-mailwarmer'), // Title of the meta box
'rl_mailwarmer_render_conversation_box', // Callback to display the box content
'campaign', // Post type
'side', // Context: side, normal, or advanced
'default' // Priority
);
});
/**
* Render the Generate Conversation meta box.
*
* @param WP_Post $post The current post object.
*/
function rl_mailwarmer_render_conversation_box($post) {
// Add a nonce for security
wp_nonce_field('rl_generate_conversation_nonce', 'rl_generate_conversation_nonce_field');
// Meta box form
?>
<div>
<label for="initiated_by"><?php esc_html_e('Initiated By (optional)', 'rl-mailwarmer'); ?></label>
<input type="text" id="initiated_by" name="initiated_by" class="widefat" placeholder="Email address or account ID">
<label for="subject"><?php esc_html_e('Subject (optional)', 'rl-mailwarmer'); ?></label>
<input type="text" id="subject" name="subject" class="widefat" placeholder="Subject line">
<label for="length"><?php esc_html_e('Length (optional)', 'rl-mailwarmer'); ?></label>
<select id="length" name="length" class="widefat">
<option value=""><?php esc_html_e('Select length', 'rl-mailwarmer'); ?></option>
<option value="extra-long"><?php esc_html_e('Extra Long', 'rl-mailwarmer'); ?></option>
<option value="long"><?php esc_html_e('Long', 'rl-mailwarmer'); ?></option>
<option value="medium"><?php esc_html_e('Medium', 'rl-mailwarmer'); ?></option>
<option value="short"><?php esc_html_e('Short', 'rl-mailwarmer'); ?></option>
<option value="single"><?php esc_html_e('Single', 'rl-mailwarmer'); ?></option>
</select>
<button type="button" id="generate-conversation" class="button button-primary" style="margin-top: 10px;">
<?php esc_html_e('Generate Conversation', 'rl-mailwarmer'); ?>
</button>
</div>
<?php
}
add_action('admin_enqueue_scripts', function ($hook) {
global $post;
if (($hook === 'post.php' || $hook === 'post-new.php') && $post->post_type === 'campaign') {
wp_enqueue_script(
'rl-generate-conversation',
RL_MAILWARMER_URL . 'js/generate-conversation.js',
['jquery'],
null,
true
);
wp_localize_script('rl-generate-conversation', 'rlGenerateConversation', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('rl_generate_conversation_nonce'),
'post_id' => $post->ID,
]);
}
});
add_action('wp_ajax_rl_generate_conversation', function () {
check_ajax_referer('rl_generate_conversation_nonce', 'nonce');
$campaign_id = intval($_POST['post_id']);
$args = [
'initiated_by' => sanitize_text_field($_POST['initiated_by'] ?? ''),
'subject' => sanitize_text_field($_POST['subject'] ?? ''),
'length' => sanitize_text_field($_POST['length'] ?? ''),
];
$result = RL_MailWarmer_Campaign_Helper::generate_conversation($campaign_id, $args);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message()]);
}
wp_send_json_success(['conversation_id' => $result]);
});
/**
* Add the "Generate Blueprint" metabox to the Campaign edit page.
*/
add_action('add_meta_boxes', function () {
add_meta_box(
'rl_mailwarmer_generate_blueprint',
__('Generate Blueprint', 'rl-mailwarmer'),
'rl_mailwarmer_render_generate_blueprint_metabox',
'campaign', // Post type
'side',
'default'
);
});
/**
* Render the "Generate Blueprint" metabox.
*
* @param WP_Post $post The current post object.
*/
function rl_mailwarmer_render_generate_blueprint_metabox($post) {
wp_nonce_field('rl_mailwarmer_generate_blueprint_nonce', 'rl_mailwarmer_generate_blueprint_nonce_field');
?>
<label for="blueprint_length"><?php esc_html_e('Number of Responses:', 'rl-mailwarmer'); ?></label>
<input
type="number"
id="blueprint_length"
name="blueprint_length"
value="1"
min="1"
step="1"
class="widefat"
/>
<button id="generate-blueprint" class="button button-primary" style="margin-top: 10px;">
<?php esc_html_e('Generate Blueprint', 'rl-mailwarmer'); ?>
</button>
<div id="blueprint-result" style="margin-top: 10px;"></div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const button = document.getElementById('generate-blueprint');
const resultDiv = document.getElementById('blueprint-result');
const inputLength = document.getElementById('blueprint_length');
button.addEventListener('click', function (e) {
e.preventDefault();
button.disabled = true;
resultDiv.textContent = '<?php esc_html_e('Processing...', 'rl-mailwarmer'); ?>';
const length = inputLength.value;
jQuery.post(ajaxurl, {
action: 'rl_mailwarmer_generate_blueprint',
post_id: <?php echo esc_js($post->ID); ?>,
length: length,
security: '<?php echo esc_js(wp_create_nonce('rl_mailwarmer_generate_blueprint_nonce')); ?>'
}, function (response) {
button.disabled = false;
if (response.success) {
resultDiv.textContent = '<?php esc_html_e('Blueprint generated successfully!', 'rl-mailwarmer'); ?>';
} else {
resultDiv.textContent = '<?php esc_html_e('Error:', 'rl-mailwarmer'); ?> ' + response.data.message;
}
});
});
});
</script>
<?php
}
/**
* AJAX handler for generating conversation blueprints.
*/
add_action('wp_ajax_rl_mailwarmer_generate_blueprint', function () {
// Verify nonce
if (!check_ajax_referer('rl_mailwarmer_generate_blueprint_nonce', 'security', false)) {
wp_send_json_error(['message' => __('Invalid nonce.', 'rl-mailwarmer')]);
}
// Check permissions
if (!current_user_can('edit_post', $_POST['post_id'])) {
wp_send_json_error(['message' => __('Permission denied.', 'rl-mailwarmer')]);
}
// Validate inputs
$post_id = intval($_POST['post_id']);
$length = intval($_POST['length']);
if ($length < 1) {
wp_send_json_error(['message' => __('Invalid length specified.', 'rl-mailwarmer')]);
}
try {
// Generate the blueprint
RL_MailWarmer_Campaign_Helper::generate_conversation_blueprint($post_id, $length);
wp_send_json_success();
} catch (Exception $e) {
wp_send_json_error(['message' => $e->getMessage()]);
}
});
/**
* Add a flexbox grid displaying the campaign timeline with gradient colors based on intensity.
*/
add_action('edit_form_after_title', 'rl_mailwarmer_display_campaign_timeline');
function rl_mailwarmer_display_campaign_timeline($post) {
// log_to_file("rl_mailwarmer_display_campaign_timeline - Running for {$post->post_title}");
if ($post->post_type !== 'campaign') {
return;
}
// Fetch campaign timeline
$campaign_timeline = get_post_meta($post->ID, 'campaign_timeline_json', true);
if (empty($campaign_timeline)) {
echo '<p>' . esc_html__('No timeline available for this campaign.', 'rl-mailwarmer') . '</p>';
return;
}
$campaign_timeline = $campaign_timeline;
// Organize timeline by weeks for grid structure
$weeks = [];
$max_volume = 0;
foreach ($campaign_timeline as $date => $day) {
$max_volume = max($max_volume, $day['target_volume']);
$week_number = date('W', strtotime($date));
if (!isset($weeks[$week_number])) {
$weeks[$week_number] = [];
}
$weeks[$week_number][$date] = $day;
}
// log_to_file("edit_form_after_title - Weeks array: ", $weeks);
// Add padding days and flatten
$grid_columns = [];
foreach ($weeks as $week_number => $week) {
// Get the first date of this week
$first_date = array_key_first($week);
$first_day_num = date('N', strtotime($first_date)); // 1 (Monday) through 7 (Sunday)
// Add padding days before if needed
if ($first_day_num > 1) {
$padding_days = $first_day_num - 1;
$monday_date = date('Y-m-d', strtotime("-{$padding_days} days", strtotime($first_date)));
for ($i = 0; $i < $padding_days; $i++) {
$pad_date = date('Y-m-d', strtotime("+{$i} days", strtotime($monday_date)));
$grid_columns[$pad_date] = [
'target_volume' => 0,
'is_padding' => true
];
}
}
// Add actual days
foreach ($week as $date => $day) {
$day['is_padding'] = false;
$grid_columns[$date] = $day;
}
}
// log_to_file("rl_mailwarmer_display_campaign_timeline - Grid Columns: ", $grid_columns);
$dates = array_keys($grid_columns);
$volumes = array_map(function($day) {
return (isset($day['is_padding']) && $day['is_padding'] == true ) ? 0 : $day;
}, $grid_columns);
// log_to_file("rl_mailwarmer_display_campaign_timeline - Grid volumes: ", $volumes);
$chart_data = array_map(function($date, $volume) {
return [
'date' => date('M d', strtotime($date)),
'target' => isset($volume['target_volume']) ? $volume['target_volume'] : null ,
'sent' => isset($volume['items_sent']) ? $volume['items_sent'] : null
];
}, $dates, $volumes);
// log_to_file("Chart Data: ", $chart_data);
// Gradient colors array
// $gradients = ["#f12711", "#f24913", "#f36b15", "#f48d17", "#f5af19", "#f8c353", "#fad78c", "#fdebc6", "#ffffff"];
$gradients = ["#ffffff", "#fdebc6", "#fad78c", "#f8c353", "#f5af19", "#f48d17", "#f36b15", "#f24913", "#f12711"];
// Generate the grid
?>
<div class="timeline-grid">
<?php
foreach ($grid_columns as $date => $day):
if (!empty($day['is_padding'])):
?>
<div class="day padding">
<div class="date"><?php echo esc_html(date('M d, Y', strtotime($date))); ?></div>
<div class="volume">-</div>
</div>
<?php
else:
$intensity = ($day['target_volume'] / $max_volume) * 100;
$gradient_index = min(floor($intensity / (100 / (count($gradients) - 1))), count($gradients) - 1);
$background_color = $gradients[$gradient_index];
?>
<div class="day" style="background-color: <?php echo esc_attr($background_color); ?>;">
<div class="date"><?php echo esc_html(date('M d, Y', strtotime($date))); ?></div>
<div class="volume"><?php echo esc_html($day['target_volume']); ?> Emails</div>
<div class="change"><?php echo esc_html($day['percent_daily_change']); ?>% </div>
</div>
<?php
endif;
endforeach;
?>
</div>
<!-- <canvas id="timelineChart" style="max-height: 300px;"></canvas> -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('timelineChart');
const data = <?php echo json_encode($chart_data); ?>;
// new Chart(ctx, {
// data: {
// labels: data.map(row => row.date),
// datasets: [{
// type: 'bar',
// label: 'Target Volume',
// data: data.map(row => row.target),
// backgroundColor: 'rgb(255, 0, 0)',
// borderColor: 'rgb(255, 0, 0)',
// borderWidth: 1,
// order: 10,
// fill: false
// }, {
// type: 'bar',
// label: 'Emails Sent',
// data: data.map(row => row.sent),
// backgroundColor: 'rgba(0, 255, 0)',
// borderColor: 'rgba(0, 255, 0, 0.8)',
// borderWidth: 1,
// order: 1
// }]
// },
// options: {
// responsive: true,
// scales: {
// y: {
// beginAtZero: true,
// title: {
// display: true,
// text: 'Number of Emails'
// }
// },
// x: {
// stacked: true
// }
// }
// }
// });
</script>
<?php
}
/**
* Delete related conversations & messages when a campaign is deleted
*/
add_action('wp_trash_post', 'mailferno_delete_campaign_messages');
add_action('before_delete_post', 'mailferno_delete_campaign_messages');
function mailferno_delete_campaign_messages($post_id) {
// Check if the post is of type 'campaign'
if (get_post_type($post_id) === 'campaign') {
log_to_file("mailferno_delete_campaign_messages - Deleting conversations & messages for campaign ID: {$post_id}");
RL_MailWarmer_DB_Helper::delete_all_conversations_messages($post_id);
}
}
// Cal-Heatmap
// add_action('edit_form_after_title', function ($post) {
// if ($post->post_type === 'campaign') {
// echo '<div id="campaign-timeline-heatmap" style="width: 100%; height: 300px; border:1px solid #fff;"></div>';
// }
// });
// add_action('admin_enqueue_scripts', function ($hook) {
// global $post;
// if (($hook === 'post.php' || $hook === 'post-new.php') && $post->post_type === 'campaign') {
// // Enqueue D3.js (required for Cal-Heatmap)
// wp_enqueue_script(
// 'd3-js',
// 'https://d3js.org/d3.v7.min.js',
// [],
// '7.0.0',
// true
// );
// // Enqueue Cal-Heatmap
// wp_enqueue_script(
// 'cal-heatmap',
// 'https://unpkg.com/cal-heatmap/dist/cal-heatmap.min.js',
// ['d3-js'],
// '4.2.4',
// true
// );
// // Enqueue Cal-Heatmap CSS
// wp_enqueue_style(
// 'cal-heatmap',
// 'https://unpkg.com/cal-heatmap/dist/cal-heatmap.css',
// [],
// '4.2.4'
// );
// // Custom heatmap script
// wp_enqueue_script(
// 'rl-mailwarmer-campaign-heatmap',
// RL_MAILWARMER_URL . 'js/campaign-timeline-heatmap.js',
// ['cal-heatmap', 'jquery'],
// null,
// true
// );
// // Pass data to the script
// wp_localize_script('rl-mailwarmer-campaign-heatmap', 'rlMailWarmerHeatmap', [
// 'ajax_url' => admin_url('admin-ajax.php'),
// 'nonce' => wp_create_nonce('campaign_timeline_nonce'),
// 'post_id' => $post->ID,
// ]);
// }
// });
// add_action('wp_ajax_rl_mailwarmer_get_timeline', function () {
// // Verify nonce
// if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'campaign_timeline_nonce')) {
// wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer'));
// }
// // Validate and sanitize input
// $post_id = intval($_POST['post_id']);
// if (!$post_id || get_post_type($post_id) !== 'campaign') {
// wp_send_json_error(__('Invalid campaign ID.', 'rl-mailwarmer'));
// }
// // Retrieve the timeline
// $timeline_json = get_post_meta($post_id, 'campaign_timeline', true);
// $timeline = json_decode($timeline_json, true);
// if (!$timeline) {
// wp_send_json_error(__('No timeline data found.', 'rl-mailwarmer'));
// }
// // Convert timeline to Cal-Heatmap format (timestamp => value)
// $heatmap_data = [];
// foreach ($timeline as $date ) {
// $timestamp = "'" . strtotime($date['date']) . "'"; // Convert to UNIX timestamp
// $heatmap_data[$timestamp] = $date['target_volume'];
// }
// wp_send_json_success($timeline);
// });