rl-warmup-plugin/includes/class-rl-mailwarmer-db-helper.php
ruben de40085318 Implement AI-powered conversation generation and improved campaign timeline management
- Add OpenAI integration for realistic email conversation generation
- Enhance campaign timeline algorithm with natural distribution patterns
- Improve message handling with Symfony Mailer components
- Add conversation blueprint system for structured email threads
- Implement visual timeline with heatmap for campaign tracking

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-03-07 01:49:49 -06:00

428 lines
No EOL
15 KiB
PHP

<?php
class RL_MailWarmer_DB_Helper {
private static $conversations_table = 'rl_mailwarmer_conversations';
private static $messages_table = 'rl_mailwarmer_messages';
private static $backups_table = 'rl_mailwarmer_dns_backups';
private static $health_reports_table = 'rl_mailwarmer_health_reports';
/**
* Create necessary database tables.
*/
public static function create_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Conversation table
$conversation_sql = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}" . self::$conversations_table . "` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
campaign_id BIGINT(20) UNSIGNED NOT NULL,
email_account_id BIGINT(20) UNSIGNED NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
status VARCHAR(20) DEFAULT 'new' NOT NULL,
first_message_timestamp DATETIME DEFAULT NULL,
prompt LONGTEXT DEFAULT NULL,
conversation_steps LONGTEXT DEFAULT NULL,
ai_response LONGTEXT DEFAULT NULL,
PRIMARY KEY (id),
KEY campaign_id_idx (campaign_id),
KEY status_idx (status),
KEY first_message_timestamp_idx (first_message_timestamp),
INDEX email_account_id_idx (email_account_id)
) $charset_collate;";
// Message table
$message_sql = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}" . self::$messages_table . "` (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
campaign_id BIGINT(20) UNSIGNED NOT NULL,
conversation_id BIGINT(20) UNSIGNED NOT NULL,
email_account_id BIGINT(20) UNSIGNED NOT NULL,
scheduled_for_timestamp DATETIME NOT NULL,
status ENUM('pending', 'in_progress', 'sent', 'failed') NOT NULL DEFAULT 'pending',
from_email VARCHAR(255) NOT NULL,
to_email TEXT NOT NULL,
cc TEXT NULL,
subject VARCHAR(255) NOT NULL,
body LONGTEXT NOT NULL,
PRIMARY KEY (id),
INDEX scheduled_idx (scheduled_for_timestamp, status),
INDEX conversation_id_idx (conversation_id),
INDEX campaign_id_idx (campaign_id),
INDEX email_account_id_idx (email_account_id)
) $charset_collate;";
// Backup table
$backup_sql = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}" . self::$backups_table . "` (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
domain_id BIGINT UNSIGNED NOT NULL,
record_name VARCHAR(255) NOT NULL,
record_type VARCHAR(50) NOT NULL,
record_content LONGTEXT NOT NULL,
created_at DATETIME NOT NULL,
INDEX (domain_id),
INDEX (record_name),
INDEX (record_type)
) $charset_collate;";
// Backup table
$health_report_sql = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}" . self::$health_reports_table . "` (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
domain_id BIGINT UNSIGNED NOT NULL,
report_content LONGTEXT NOT NULL,
created_at DATETIME NOT NULL,
last_checked DATETIME NOT NULL,
INDEX (domain_id)
) $charset_collate;";
// DNS Backup table
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($conversation_sql);
dbDelta($message_sql);
dbDelta($backup_sql);
dbDelta($health_report_sql);
}
/**
* Insert a conversation record.
*/
public static function insert_conversation($conversation_data) {
global $wpdb;
$wpdb->insert("{$wpdb->prefix}" . self::$conversations_table, $conversation_data);
return $wpdb->insert_id;
}
/**
* Update a conversation record.
*
* @param int $conversation_id The ID of the conversation to update.
* @param array $update_data An associative array of columns and values to update.
*
* @return int|false The number of rows updated, or false on error.
*/
public static function update_conversation($conversation_id, $update_data) {
global $wpdb;
// Ensure that $conversation_id is a valid integer
if (!is_int($conversation_id) || $conversation_id <= 0) {
return false;
}
// Update the table with the provided data
$updated = $wpdb->update(
"{$wpdb->prefix}" . self::$conversations_table,
$update_data,
['id' => $conversation_id] // WHERE clause
);
return $updated !== false ? $updated : false;
}
/**
* Insert a message record.
*/
public static function insert_message($message_data) {
global $wpdb;
// log_to_file("insert_message - Message body: $body");
// $wpdb->insert(
// "{$wpdb->prefix}rl_mailwarmer_message",
// [
// 'campaign_id' => $campaign_id,
// 'conversation_id' => $conversation_id,
// 'scheduled_for_timestamp' => $scheduled_for,
// 'status' => 'pending',
// 'from_email' => $from_email,
// 'to_email' => $to_email,
// 'cc' => $cc,
// 'subject' => $subject,
// 'body' => $body,
// 'first_message' => $first_message ? 1 : 0,
// ],
// ['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d']
// );
$wpdb->insert("{$wpdb->prefix}" . self::$messages_table, $message_data);
return $wpdb->insert_id;
}
/**
* Delete all conversations and messages for a given campaign ID.
*
* @param int $campaign_id The ID of the campaign.
*/
public static function delete_all_conversations_messages($campaign_id) {
global $wpdb;
// Ensure campaign_id is an integer
$campaign_id = (int) $campaign_id;
$conversations_table = $wpdb->prefix . self::$conversations_table;
$messages_table = $wpdb->prefix . self::$messages_table;
// Delete messages
$delete_messages_result = $wpdb->query(
$wpdb->prepare(
"DELETE FROM $messages_table WHERE campaign_id = %d",
$campaign_id
)
);
log_to_file("delete_all_conversations_messages - delete_messages_result: ", $delete_messages_result);
// Delete conversations
$delete_conversations_result = $wpdb->query(
$wpdb->prepare(
"DELETE FROM $conversations_table WHERE campaign_id = %d",
$campaign_id
)
);
log_to_file("delete_all_conversations_messages - delete_conversations_result: ", $delete_conversations_result);
}
/**
* Delete all future conversations and messages for a given campaign ID.
*
* @param int $campaign_id The ID of the campaign.
*/
public static function delete_future_conversations_messages($campaign_id) {
global $wpdb;
$conversations_table = $wpdb->prefix . self::$conversations_table;
$messages_table = $wpdb->prefix . self::$messages_table;
$current_time = current_time('mysql');
// Delete future messages
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $messages_table WHERE campaign_id = %d AND scheduled_for_timestamp > %s",
$campaign_id,
$current_time
)
);
// Delete future conversations
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $conversations_table WHERE campaign_id = %d AND first_message_timestamp > %s",
$campaign_id,
$current_time
)
);
}
/**
* Delete all conversations and messages older than X days.
*
* @param int $days The number of days.
*/
public static function delete_old_conversations_messages($days) {
global $wpdb;
$conversations_table = $wpdb->prefix . self::$conversations_table;
$messages_table = $wpdb->prefix . self::$messages_table;
$threshold_date = date('Y-m-d H:i:s', strtotime("-$days days"));
// Delete old messages
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $messages_table WHERE scheduled_for_timestamp < %s",
$threshold_date
)
);
// Delete old conversations
$wpdb->query(
$wpdb->prepare(
"DELETE FROM $conversations_table WHERE first_message_timestamp < %s",
$threshold_date
)
);
}
/**
* Fetch pending messages.
*/
public static function fetch_pending_messages($limit = 100) {
global $wpdb;
$sql = $wpdb->prepare(
"SELECT * FROM `{$wpdb->prefix}" . self::$messages_table . "`
WHERE scheduled_for_timestamp <= %s AND status = 'pending'
LIMIT %d",
current_time('mysql'),
$limit
);
return $wpdb->get_results($sql, ARRAY_A);
}
// Get Message Counts
public static function get_message_counts_by_date($campaign_id) {
global $wpdb;
$messages_table = $wpdb->prefix . self::$messages_table;
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT
DATE(scheduled_for_timestamp) AS message_date,
COUNT(*) AS message_count
FROM
$messages_table
WHERE
campaign_id = %d
GROUP BY
DATE(scheduled_for_timestamp)
ORDER BY
message_date ASC",
$campaign_id
),
ARRAY_A
);
return $results;
}
/**
* Insert DNS backups into the database.
*
* @param mixed $domain The domain ID or post object.
* @param array $records The DNS records to back up.
* @return bool True on success, false on failure.
*/
public static function insert_dns_backup($domain, $record) {
global $wpdb;
$backups_table = $wpdb->prefix . 'rl_mailwarmer_dns_backups';
$domain_post = is_object($domain) ? $domain : RL_MailWarmer_Domain_Helper::get_domain_post($domain);
if (!$domain_post) {
throw new Exception(__('Invalid domain specified.', 'rl-mailwarmer'));
}
// log_to_file("insert_dns_backup - Attemting to backup record: ", $record);
$existing = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM $backups_table WHERE domain_id = %d AND record_name = %s AND record_type = %s ORDER BY created_at DESC LIMIT 1",
$domain_post->ID,
$record['name'],
$record['type']
),
ARRAY_A
);
if ($existing && $existing['record_content'] === $record['content']) {
// log_to_file("insert_dns_backup - New & Old content are the same. Skipping insert and returning TRUE");
// return true;
return $existing['id'];
// continue; // Skip unchanged records
}
try {
$wpdb->insert($backups_table, [
'domain_id' => $domain_post->ID,
'record_name' => $record['name'],
'record_type' => $record['type'],
'record_content' => $record['content'],
'created_at' => current_time('mysql'),
]);
} catch (Exception $e) {
error_log(__('insert_dns_backup - Failed to insert new DNS backup record: ', 'rl-mailwarmer') . $e->getMessage());
}
return $wpdb->insert_id;
// return true;
}
/**
* Insert health report backups into the database.
*
* @param mixed $domain The domain ID or post object.
* @param array $records The DNS records to back up.
* @return bool True on success, false on failure.
*/
public static function insert_health_report_backup($domain, $report) {
global $wpdb;
$health_reports_table = $wpdb->prefix . 'rl_mailwarmer_health_reports';
$domain_post = is_object($domain) ? $domain : RL_MailWarmer_Domain_Helper::get_domain_post($domain);
if (!$domain_post) {
throw new Exception(__('Invalid domain specified.', 'rl-mailwarmer'));
}
// log_to_file("insert_health_report_backup - Attempting to save health report for {$domain_post->post_title}: ");
try {
// log_to_file("insert_health_report_backup - Fetching existing rows.");
$existing = $wpdb->get_row(
$wpdb->prepare(
"SELECT * FROM $health_reports_table WHERE domain_id = %d ORDER BY created_at DESC LIMIT 1",
$domain_post->ID
),
ARRAY_A
);
// log_to_file("insert_health_report_backup - Done fetching existing rows.");
if ($existing && $existing['report_content'] === $report) {
// log_to_file("insert_health_report_backup - New & Old content are the same. Skipping insert and returning ID of existing record");
// Prepare the data
$data = [
'last_checked' => current_time('mysql'), // Save the JSON response as a string
];
$where = [
'id' => $existing['id'],
];
// Update the database
try {
$result = $wpdb->update(
$health_reports_table,
$data,
$where,
['%s'],
['%d']
);
} catch (Exception $e) {
log_to_file("insert_health_report_backup - Error updating existing database entry.");
throw new Exception(__('insert_health_report_backup - Error updating existing database entry: ', 'rl-mailwarmer') . $e->getMessage());
}
return $existing['id'];
// continue; // Skip unchanged records
}
} catch (Exception $e) {
log_to_file("insert_health_report_backup - Error fetching existing rows.");
throw new Exception(__('insert_health_report_backup - Unable to fetch existing records: ', 'rl-mailwarmer') . $e->getMessage());
}
try {
$current_time = current_time('mysql');
$data = [
'domain_id' => $domain_post->ID,
'report_content' => $report,
'created_at' => $current_time,
'last_checked' => $current_time,
];
$result = $wpdb->insert(
$health_reports_table,
$data
);
if ($result === false) {
return new WP_Error('db_update_failed', __('Failed to update the conversation steps.', 'rl-mailwarmer'));
}
return $result;
} catch (Exception $e) {
throw new Exception(__('insert_health_report_backup - Failed to insert new health report record: ', 'rl-mailwarmer') . $e->getMessage());
// error_log(__('insert_dns_backup - Failed to insert new health report record: ', 'rl-mailwarmer') . $e->getMessage());
}
return false;
}
}