rl-warmup-plugin/includes/class-rl-mailwarmer-email-account-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

637 lines
26 KiB
PHP

<?php
/**
* Helper functions for the email-account post type.
*/
// require 'vendor/autoload.php';
use phpseclib3\Net\SSH2;
use phpseclib3\Crypt\PublicKeyLoader;
if (!defined('ABSPATH')) {
exit;
}
/**
* RL_MailWarmer_Email_Helper Class
*
* Handles email account management and mail operations.
*/
class RL_MailWarmer_Email_Helper
{
/**
* Modify an email account post.
*
* Creates, updates, or deletes an email account post and its associated metadata.
*
* @param array $args {
* Arguments for modifying the email account.
*
* @type int $post_id Optional. Post ID to update or delete. Required for updates or deletions.
* @type string $action Required. Action to perform: 'create', 'update', or 'delete'.
* @type string $email Optional. Email address for the account (used as post title).
* @type array $metadata Optional. Additional metadata to save with the post.
* }
* @return int|bool Post ID on success, false on failure.
*/
// Create an Email Account
// $post_id = RL_MailWarmer_Email_Helper::modify_email_account([
// 'action' => 'create',
// 'email' => 'johndoe@example.com',
// 'metadata' => [
// 'full_name' => 'John Doe',
// 'mail_password' => 'securepassword123',
// 'email_provider' => 123, // Post ID of the email provider
// 'smtp_server' => 'smtp.example.com',
// 'smtp_port' => 587,
// 'imap_server' => 'imap.example.com',
// 'imap_port' => 993,
// ],
// ]);
// // Update an Email Account
// $result = RL_MailWarmer_Email_Helper::modify_email_account([
// 'action' => 'update',
// 'post_id' => $post_id,
// 'metadata' => [
// 'full_name' => 'Jane Doe',
// 'mail_password' => 'newsecurepassword123',
// 'smtp_status' => 'connected',
// ],
// ]);
// // Delete an Email Account
// $result = RL_MailWarmer_Email_Helper::modify_email_account([
// 'action' => 'delete',
// 'post_id' => $post_id,
// ]);
public static function modify_email_account(array $args)
{
// Validate required arguments
if (empty($args['action']) || !in_array($args['action'], ['create', 'update', 'delete'], true)) {
throw new InvalidArgumentException('Invalid or missing action.');
}
/*
* Add validation to only delete email-account posts
*
*/
$action = $args['action'];
$post_id = $args['post_id'] ?? null;
// Handle delete action
if ($action === 'delete') {
if (!$post_id) {
throw new InvalidArgumentException('Post ID is required for deletion.');
}
return wp_delete_post($post_id, true);
}
// Validate fields for create/update
$post_data = [
'post_type' => 'email-account',
'post_status' => 'publish',
];
// For "create", ensure no existing post with the same title
if ($action === 'create') {
if (empty($args['email'])) {
throw new InvalidArgumentException('Email is required for creating a new account.');
}
$existing_post = get_page_by_title($args['email'], OBJECT, 'email-account');
if ($existing_post) {
throw new RuntimeException('An email account with this title already exists.');
}
$post_data['post_title'] = $args['email'];
} elseif ($action === 'update') {
if (!$post_id) {
throw new InvalidArgumentException('Post ID is required for updates.');
}
$post_data['ID'] = $post_id;
}
// Assemble metadata
$meta_args = $args['metadata'] ?? [];
// Generate a random password if mail_password is not provided
if (empty($meta_args['mail_password'])) {
$meta_args['mail_password'] = bin2hex(random_bytes(8)); // 16-character password
}
$post_data['meta_input'] = array_map('sanitize_text_field', $meta_args);
// Save or update the post
$post_id = wp_insert_post($post_data);
if (is_wp_error($post_id)) {
throw new RuntimeException('Failed to save email account post: ' . $post_id->get_error_message());
}
return $post_id;
}
/**
* Check mail login credentials for an email account.
*
* Validates IMAP/SMTP connection settings for the given email account.
*
* @param mixed $email_account The email-account post object or ID.
* @param string|null $protocol Optional. The protocol to validate ('IMAP' or 'SMTP'). Defaults to both.
* @return array|WP_Error Validation results for IMAP and/or SMTP or WP_Error on failure.
*/
public static function check_mail_login($email_account, $protocol = null)
{
// log_to_file("check_mail_login - Email account id: {$email_account}");
// Get the post object
$post = is_numeric($email_account) ? get_post($email_account) : $email_account;
if (!$post || $post->post_type !== 'email-account') {
// log_to_file("check_mail_login - Not an email account post-type");
return new WP_Error('invalid_post', __('Invalid email account post.', 'rl-mailwarmer'));
}
// Fetch email provider and override defaults with saved values
$email_provider_id = get_post_meta($post->ID, 'email_provider', true);
// log_to_file("check_mail_login - email_provider_id: {$email_provider_id}");
// log_to_file("check_mail_login - Email Provider ID $email_provider_id");
$defaults = $email_provider_id ? self::get_provider_defaults($email_provider_id) : [];
// log_to_file("check_mail_login - Email Provider Defaults: ", $defaults);
// Fetch saved settings
$saved_settings = [
'email_address' => $post->post_title,
'full_name' => get_post_meta($post->ID, 'full_name', true),
'email_signature' => get_post_meta($post->ID, 'email_signature', true),
'mail_password' => get_post_meta($post->ID, 'mail_password', true),
'imap_password' => get_post_meta($post->ID, 'imap_password', true),
'imap_server' => get_post_meta($post->ID, 'imap_server', true),
'imap_port' => get_post_meta($post->ID, 'imap_port', true),
'smtp_password' => get_post_meta($post->ID, 'smtp_password', true),
'smtp_server' => get_post_meta($post->ID, 'smtp_server', true),
'smtp_port' => get_post_meta($post->ID, 'smtp_port', true),
];
// Merge saved settings with defaults
$settings = array_merge($defaults, array_filter($saved_settings));
// log_to_file("check_mail_login - Using settings: ", $settings);
$results = [];
// Validate IMAP connection if required
if ($protocol === null || strtoupper($protocol) === 'IMAP') {
$imap_result = self::validate_imap_connection($settings);
// log_to_file("check_mail_login - IMAP Result for " . $post->post_title . ": ", $imap_result);
$results['IMAP'] = $imap_result ? __('SUCCESS', 'rl-mailwarmer') : $imap_result->get_error_message();
update_post_meta($post->ID, 'imap_status', $results['IMAP']);
}
// Validate SMTP connection if required
if ($protocol === null || strtoupper($protocol) === 'SMTP') {
$smtp_result = self::validate_smtp_connection($settings, true);
// log_to_file("check_mail_login - SMTP Result for " . $post->post_title . ": ", $smtp_result);
$results['SMTP'] = $smtp_result ? __('SUCCESS', 'rl-mailwarmer') : $smtp_result->get_error_message();
update_post_meta($post->ID, 'smtp_status', $results['SMTP']);
}
// log_to_file("check_mail_login - Full Results for " . $post->post_title . ": ", $results);
return $results;
}
/**
* Fetch default settings for an email provider.
*
* @param int $email_provider_id The post ID of the email provider.
* @return array The default server settings.
*/
public static function get_provider_defaults($email_provider_id)
{
return [
'imap_server' => get_post_meta($email_provider_id, 'default_imap_server', true),
'imap_port' => get_post_meta($email_provider_id, 'default_imap_port', true),
'smtp_server' => get_post_meta($email_provider_id, 'default_smtp_server', true),
'smtp_port' => get_post_meta($email_provider_id, 'default_smtp_port', true),
];
}
/**
* Validate an IMAP connection for an email account.
*
* @param array $settings The server settings (email_account, imap_server, imap_port, imap_password).
* @return bool True if the connection is successful, false otherwise.
*/
public static function validate_imap_connection($settings)
{
if ( empty($settings['email_address']) || empty($settings['mail_password']) || empty($settings['imap_server']) || empty($settings['imap_port']) ) {
// log_to_file("validate_imap_connection - Incomplete connection information");
return false; // Missing required settings
}
$password = $settings['mail_password'];
$email = $settings['email_address'];
// log_to_file("validate_imap_connection - Checking IMAP connection for {$email} using: ", $settings);
$imap_server = '{' . $settings['imap_server'] . ':' . $settings['imap_port'] . '/imap/ssl}';
// log_to_file("validate_imap_connection - Trying to open stream for {$email} : {$password} @ {$imap_server}");
// Try connecting to the IMAP server
$imap_stream = @imap_open(
$imap_server,
$email,
$password
);
if ($imap_stream) {
// log_to_file("validate_imap_connection - Stream opened. Closing!");
imap_close($imap_stream); // Close connection if successful
return true;
}
// log_to_file("validate_imap_connection - Unable to open stream");
return false; // Connection failed
}
/**
* Validate an SMTP connection for an email account using Symfony Mailer.
*
* @param string $email The email address.
* @param array $settings The server settings (smtp_server, smtp_port, smtp_password).
* @return bool True if the connection is successful, false otherwise.
*/
public static function validate_smtp_connection($settings, $send_test_email = false)
{
if ( empty($settings['email_address']) || empty($settings['mail_password']) || empty($settings['smtp_server']) || empty($settings['smtp_port']) ) {
// log_to_file("validate_smtp_connection - Incomplete connection information");
return false; // Missing required settings
}
$email = $settings['email_address'];
$password = $settings['mail_password'];
// log_to_file("validate_smtp_connection - Settings for {$email}: ", $settings);
$signature = str_replace('\n', PHP_EOL, $settings['email_signature']);
$test_to_email = "ruben@redlotusaustin.com";
$email_body = "<p>This is a test email to verify SMTP connection for {$email}\n\n{$signature}</p><br /><br /><span style='font-size:0.5rem;'>MFTID-0000000000000000</span>";
try {
// Create the SMTP transport
$transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport(
$settings['smtp_server'],
$settings['smtp_port']
);
// Set authentication details
$transport->setUsername($email);
$transport->setPassword($password);
// Create the mailer
$mailer = new Symfony\Component\Mailer\Mailer($transport);
// Send a test email
$test_email = (new Symfony\Component\Mime\Email())
->from("{$settings['full_name']} <{$email}>")
->to($test_to_email)
->subject('SMTP Connection Test for ' . $email)
->html($email_body);
if ($send_test_email) {
$mailer->send($test_email);
}
return true;
} catch (Exception $e) {
error_log('SMTP validation failed: ' . $e->getMessage());
return false;
}
}
/**
* Generate random email accounts for the specified domain.
*
* @param string $domain The domain name to use for the email accounts.
* @param int $qty The number of email accounts to generate. Defaults to 1.
* @return array List of generated names and email addresses.
* @throws Exception If name pools are empty or post creation fails.
*/
public static function generate_random_accounts($domain, $qty = 1)
{
$domain_post = get_post($domain);
// Fetch name pools from ACF options
$first_name_pool = get_field('valid_first_name_pool', 'option');
$last_name_pool = get_field('valid_last_name_pool', 'option');
if (empty($first_name_pool) || empty($last_name_pool)) {
throw new Exception(__('Name pools are empty. Please configure them in the ACF options.', 'rl-mailwarmer'));
}
$first_names = explode(',', $first_name_pool); // Assume comma-separated list
$last_names = explode(',', $last_name_pool);
$generated_accounts = [];
for ($i = 0; $i < $qty; $i++) {
// Generate a random name
$first_name = trim($first_names[array_rand($first_names)]);
$last_name = trim($last_names[array_rand($last_names)]);
$full_name = "{$first_name} {$last_name}";
// Generate a semi-random email address
$email_formats = [
"{$first_name}{$last_name}",
"{$first_name}.{$last_name}",
substr($first_name, 0, 1) . ".{$last_name}",
"{$first_name}.l",
substr($first_name, 0, 1) . $last_name,
];
$email_local_part = strtolower($email_formats[array_rand($email_formats)]);
$email_address = "{$email_local_part}@{$domain_post->post_title}";
// Generate a random password
$random_password = wp_generate_password(16, false, false);
$signature = self::generate_random_email_signature($first_name, $last_name, $email_address);
$servers[] = 108;
$mailferno_default_email_provider = 92;
// Create the email-account post
$post_id = wp_insert_post([
'post_type' => 'email-account',
'post_status' => 'publish',
'post_title' => $email_address,
'meta_input' => [
'full_name' => "{$first_name} {$last_name}",
'mail_password' => $random_password,
'email_signature' => $signature,
'domain' => $domain_post->ID,
'include_in_reply_pool' => true,
'include_in_cc_pool' => true,
'include_in_warmup_pool' => true,
'servers' => $servers,
'email_provider' => $mailferno_default_email_provider,
],
]);
// log_to_file("generate_random_accounts - Added email account to local server: $post_id");
if ($post_id && !is_wp_error($post_id)) {
$generated_accounts[] = [
'name' => "{$first_name} {$last_name}",
'email' => $email_address,
'password' => $random_password,
'post_id' => $post_id,
];
// log_to_file("generate_random_accounts - {$first_name} {$last_name}\t{$email_address}\t{$random_password}");
$add_account_result = self::modify_email_account_on_server($post_id, 'create');
// log_to_file("generate_random_accounts - Result of attempting to add account to remote server: ", $add_account_result);
if ( isset($add_account_result['errors']) ) {
// log_to_file("generate_random_accounts - Error modifying account on remote server: ", $add_account_result['errors']);
} else {
// log_to_file("generate_random_accounts - Added $email_address to remote server: ", $add_account_result);
$login_test_results = self::check_mail_login($post_id);
// log_to_file("generate_random_accounts - Login test results: ", $login_test_results);
}
} else {
error_log('Failed to create email-account post: ' . print_r($post_id, true));
}
}
return $generated_accounts;
}
/**
* Generate a randomized email signature.
*
* @param string $first_name The first name of the person.
* @param string $last_name The last name of the person.
* @param string $email_address The email address of the person.
* @return string The generated email signature.
*/
private static function generate_random_email_signature($first_name, $last_name, $email_address) {
// First line variations
$first_line_variations = [
'Best regards',
'Regards',
'Yours truly',
'Sincerely',
'Warm regards',
'Kind regards',
'Best wishes',
'Respectfully',
'With gratitude',
'All the best',
'Cheers',
'Thank you',
'Warm wishes',
'Yours sincerely',
'Cordially',
'With appreciation',
'Many thanks',
'Take care',
'Faithfully',
'Always',
];
// Randomized job titles
$job_titles = [
'Software Engineer',
'Marketing Specialist',
'Sales Manager',
'Project Coordinator',
'Product Designer',
'Customer Success Lead',
];
// Randomized phone formats
// $phone_formats = [
// '(555) %03d-%04d',
// '555-%03d-%04d',
// '+1 555 %03d %04d',
// ];
// // Social media handles (optional)
// $social_links = [
// 'LinkedIn' => 'https://linkedin.com/in/' . strtolower($first_name . $last_name),
// 'Twitter' => 'https://twitter.com/' . strtolower($first_name . $last_name),
// 'GitHub' => 'https://github.com/' . strtolower($first_name . $last_name),
// ];
// Generate random elements
$first_line = $first_line_variations[array_rand($first_line_variations)];
$job_title = $job_titles[array_rand($job_titles)];
// $phone_number = sprintf($phone_formats[array_rand($phone_formats)], rand(100, 999), rand(1000, 9999));
// $selected_social = array_rand($social_links);
// Build the email signature
$signature = "<p>{$first_line},</p>";
$signature .= "<p><strong>{$first_name} {$last_name}</strong><br>";
$signature .= "{$job_title}<br>";
$signature .= "Email: <a href='mailto:{$email_address}'>{$email_address}</a><br>";
// $signature .= "Phone: {$phone_number}<br>";
// $signature .= "<a href='{$social_links[$selected_social]}' target='_blank'>{$selected_social}</a></p>";
return $signature;
}
/**
* Modify an email account on a VirtualMin server.
*
* @param int $account_id The email-account post ID.
* @param string $action The action to perform: 'create', 'update', or 'delete'.
* @return bool|WP_Error True on success, WP_Error on failure.
*/
public static function modify_email_account_on_server($account_id, $action)
{
// Validate email-account post
$email_account = get_post($account_id);
if (!$email_account || $email_account->post_type !== 'email-account') {
return new WP_Error('invalid_account', __('Invalid email account.', 'rl-mailwarmer'));
}
// Fetch associated server posts
$domain_id = get_post_meta($account_id, 'domain', true);
if (!$domain_id) {
return new WP_Error('missing_domain', __('No associated domain found.', 'rl-mailwarmer'));
}
$server_ids = get_post_meta($domain_id, 'servers', true); // Assuming this field holds server post IDs
if (empty($server_ids) || !is_array($server_ids)) {
return new WP_Error('missing_servers', __('No associated servers found.', 'rl-mailwarmer'));
}
// Fetch email account details
$email_address = $email_account->post_title;
$password = get_post_meta($account_id, 'mail_password', true);
$full_name = get_post_meta($account_id, 'full_name', true);
[$username, $domain] = explode('@', $email_address);
// Iterate over servers and perform the action
foreach ($server_ids as $server_id) {
$server = get_post($server_id);
if (!$server || $server->post_type !== 'server') {
continue; // Skip invalid server posts
}
$server_ip = get_post_meta($server_id, 'ip_address', true);
$server_port = get_post_meta($server_id, 'ssh_port', true);
$server_user = get_post_meta($server_id, 'username', true);
$server_password = get_post_meta($server_id, 'ssh_private_key', true);
// $server_password = get_post_meta($server_id, 'password', true);
if (!$server_ip || !$server_user || !$server_password) {
return new WP_Error('missing_server_details', __('Missing server credentials.', 'rl-mailwarmer'));
}
// Build VirtualMin command
$command = "sudo virtualmin";
if ($action === 'create') {
$command .= " create-user --domain $domain --user $username --pass '$password' --real '$full_name'";
} elseif ($action === 'update') {
$command .= " modify-user --domain $domain --user $username --pass '$password' --real '$full_name'";
} elseif ($action === 'delete') {
$command .= " delete-user --domain $domain --user $username";
} else {
return new WP_Error('invalid_action', __('Invalid action specified.', 'rl-mailwarmer'));
}
// log_to_file("modify_email_account_on_server - SSH Command: ", $command);
// Execute the command via SSH
// $ssh = new phpseclib\Net\SSH2($server_ip);
// $key = new phpseclib\Crypt\PublicKeyLoader::loadPrivateKey($server_password); // Adjust for SSH key or plain password
$ssh = new SSH2($server_ip, $server_port);
if (!empty($server_password)) {
// Load the private key from the postmeta field
$key = PublicKeyLoader::loadPrivateKey($server_password);
} else {
// Fallback to password-based authentication
// $key = $server_password;
// log_to_file("modify_email_account_on_server - Server $$server_id ssh_private_key empty");
return new WP_Error('ssh_login_failed', __('No private key found!', 'rl-mailwarmer'));
}
if (!$ssh->login($server_user, $key)) {
return new WP_Error('ssh_login_failed', __('Failed to log into the server.', 'rl-mailwarmer'));
}
$output = $ssh->exec($command);
if (strpos($output, 'failed') !== false) {
return new WP_Error('command_failed', __('Failed to execute VirtualMin command.', 'rl-mailwarmer'));
}
}
return true; // Success
}
}
add_action('restrict_manage_posts', function ($post_type) {
if ($post_type === 'email-account') {
?>
<button id="test-connections-button" class="button button-primary" style="margin-left: 10px;">
<?php esc_html_e('Test Selected Connections', 'rl-mailwarmer'); ?>
</button>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('test-connections-button').addEventListener('click', function (event) {
event.preventDefault(); // Prevent form submission
const selectedAccounts = Array.from(document.querySelectorAll('.check-column input[type="checkbox"]:checked'))
.map(checkbox => checkbox.value)
.filter(value => value !== 'on'); // Exclude the "select all" checkbox
if (selectedAccounts.length === 0) {
alert('<?php esc_html_e('Please select at least one account.', 'rl-mailwarmer'); ?>');
return;
}
if (confirm('<?php esc_html_e('Are you sure you want to test connections for the selected accounts?', 'rl-mailwarmer'); ?>')) {
jQuery.post(ajaxurl, {
action: 'rl_test_connections',
account_ids: selectedAccounts
}, function (response) {
alert(response.data.message);
});
}
});
});
</script>
<?php
}
});
add_action('wp_ajax_rl_test_connections', function () {
$account_ids = isset($_POST['account_ids']) ? array_map('intval', $_POST['account_ids']) : [];
if (empty($account_ids)) {
wp_send_json_error(['message' => __('No email accounts selected.', 'rl-mailwarmer')]);
}
foreach ($account_ids as $account_id) {
// Call the check_mail_login function for this email account
$result = RL_MailWarmer_Email_Helper::check_mail_login($account_id);
}
wp_send_json_success(['message' => __('Connections tested successfully for selected accounts.', 'rl-mailwarmer')]);
});