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; } }