513 lines
20 KiB
PHP
513 lines
20 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Helper functions for the conversation messages
|
|
*/
|
|
|
|
use Symfony\Component\Mime\Email;
|
|
use Symfony\Component\Mailer\Mailer;
|
|
use Symfony\Component\Mailer\Transport\Transport;
|
|
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
|
use PhpImap\Mailbox;
|
|
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
class RL_MailWarmer_Message_Handler {
|
|
|
|
/**
|
|
* Process pending messages.
|
|
*/
|
|
public static function process_pending_messages() {
|
|
// log_to_file("process_pending_messages - Running");
|
|
global $wpdb;
|
|
|
|
// Fetch the next 100 pending messages with scheduled timestamps in the past
|
|
$table_name = $wpdb->prefix . 'rl_mailwarmer_messages';
|
|
$messages = $wpdb->get_results(
|
|
$wpdb->prepare(
|
|
"SELECT * FROM $table_name WHERE status = %s AND scheduled_for_timestamp < %s ORDER BY scheduled_for_timestamp ASC LIMIT 1",
|
|
'pending',
|
|
current_time('mysql')
|
|
),
|
|
ARRAY_A
|
|
);
|
|
|
|
if (empty($messages)) {
|
|
// log_to_file("process_pending_messages - messages empty");
|
|
return;
|
|
}
|
|
|
|
foreach ($messages as $message) {
|
|
// log_to_file("==========================================================");
|
|
try {
|
|
if (!empty($message['first_message']) && $message['first_message']) {
|
|
// log_to_file("process_pending_messages - trying send_message");
|
|
$result = self::send_message($message);
|
|
} else {
|
|
// log_to_file("process_pending_messages - trying reply_message");
|
|
$result = self::reply_message($message);
|
|
}
|
|
|
|
// Update message status to 'completed' on success
|
|
if ($result) {
|
|
self::update_message_status($message['id'], 'completed');
|
|
} else {
|
|
self::update_message_status($message['id'], 'failed');
|
|
}
|
|
} catch (Exception $e) {
|
|
// Handle errors gracefully and log them
|
|
log_to_file('Error processing message ID ' . $message['id'] . ': ' . $e->getMessage());
|
|
self::update_message_status($message['id'], 'failed');
|
|
}
|
|
sleep(3);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepare email data and fetch connection info.
|
|
*
|
|
* @param array $message The message data.
|
|
* @return array Prepared email data and connection info.
|
|
* @throws Exception If required data is missing.
|
|
*/
|
|
private static function prepare_email_data($message) {
|
|
// log_to_file("prepare_email_data - Running");
|
|
// log_to_file("prepare_email_data - Message: ", $message);
|
|
|
|
// Ensure 'from' is valid and fetch connection info
|
|
if (empty($message['from_email'])) {
|
|
throw new Exception(__('Missing "from" address in the message.', 'rl-mailwarmer'));
|
|
}
|
|
|
|
// Find the email-account post matching the 'from' address
|
|
$from_post_id = self::find_email_account_by_address($message['from_email']);
|
|
if (!$from_post_id) {
|
|
throw new Exception(__('No matching email account found for "from" address.', 'rl-mailwarmer'));
|
|
}
|
|
|
|
// Fetch connection details
|
|
// log_to_file("prepare_email_data - Getting connection info");
|
|
$mail_password = get_post_meta($from_post_id, 'mail_password', true);
|
|
$email_provider_id = get_post_meta($from_post_id, 'email_provider', true);
|
|
$connection_info = RL_MailWarmer_Email_Helper::get_provider_defaults($email_provider_id);
|
|
$connection_info['username'] = $message['from_email'];
|
|
$connection_info['mail_password'] = $mail_password;
|
|
// log_to_file("prepare_email_data - Connection Info: ", $connection_info);
|
|
|
|
// Override provider defaults with account-specific settings
|
|
foreach (['smtp_server', 'smtp_port', 'smtp_password', 'imap_server', 'imap_port', 'imap_password'] as $key) {
|
|
$meta_value = get_post_meta($from_post_id, $key, true);
|
|
if (!empty($meta_value)) {
|
|
$connection_info[$key] = $meta_value;
|
|
}
|
|
}
|
|
|
|
// Handle recipients
|
|
// log_to_file("prepare_email_data - Handling recipients");
|
|
$to_emails = is_array($message['to_email']) ? $message['to_email'] : explode(',', $message['to_email']);
|
|
$cc_emails = is_array($message['cc']) ? $message['cc'] : explode(',', $message['cc']);
|
|
|
|
return [
|
|
'connection_info' => $connection_info,
|
|
'to' => array_filter(array_map('trim', $to_emails)),
|
|
'cc' => array_filter(array_map('trim', $cc_emails)),
|
|
'subject' => $message['subject'],
|
|
'body' => $message['body'],
|
|
'from' => $message['from_email'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Find the email-account post ID by email address.
|
|
*
|
|
* @param string $email_address The email address to search for.
|
|
* @return int|null The post ID if found, or null.
|
|
*/
|
|
private static function find_email_account_by_address($email_address) {
|
|
// log_to_file("find_email_account_by_address - Searching for: $email_address");
|
|
$query = new WP_Query([
|
|
'post_type' => 'email-account',
|
|
'post_status' => 'publish',
|
|
'title' => $email_address,
|
|
'fields' => 'ids',
|
|
]);
|
|
|
|
if (!empty($query->posts)) {
|
|
return $query->posts[0];
|
|
} else {
|
|
return new WP_Error('find_email_account_by_address - Unable to find a matching email address');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the status of a message.
|
|
*
|
|
* @param int $message_id The ID of the message to update.
|
|
* @param string $status The new status ('completed', 'failed', etc.).
|
|
* @return void
|
|
*/
|
|
private static function update_message_status($message_id, $status) {
|
|
global $wpdb;
|
|
|
|
$table_name = $wpdb->prefix . 'rl_mailwarmer_messages';
|
|
|
|
$wpdb->update(
|
|
$table_name,
|
|
['status' => $status],
|
|
['id' => $message_id],
|
|
['%s'],
|
|
['%d']
|
|
);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Send the first message in a conversation.
|
|
*
|
|
* @param array $message The message details.
|
|
* @return bool True if the message is sent successfully, false otherwise.
|
|
* @throws Exception If required fields are missing or an error occurs during sending.
|
|
*/
|
|
public static function send_message($message) {
|
|
// log_to_file("send_message - Running");
|
|
// log_to_file("send_message - Message: ", $message);
|
|
|
|
// Prepare email data and connection info
|
|
$email_data = self::prepare_email_data($message);
|
|
|
|
// log_to_file("send_message - Email Data: ", $email_data);
|
|
|
|
// Extract connection info
|
|
$connection_info = $email_data['connection_info'];
|
|
|
|
// Check required fields
|
|
if (empty($email_data['to']) || empty($email_data['from']) || empty($email_data['subject']) || empty($email_data['body'])) {
|
|
throw new Exception(__('Missing required fields for sending the email.', 'rl-mailwarmer'));
|
|
}
|
|
|
|
// Create the SMTP transport
|
|
// log_to_file("send_message - Creating Transport");
|
|
$transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport(
|
|
$connection_info['smtp_server'],
|
|
$connection_info['smtp_port']
|
|
);
|
|
|
|
// Set authentication details
|
|
$transport->setUsername($connection_info['username']);
|
|
$transport->setPassword($connection_info['mail_password']);
|
|
|
|
// Create the mailer
|
|
$mailer = new Symfony\Component\Mailer\Mailer($transport);
|
|
|
|
// Build the email
|
|
// log_to_file("send_message - Building Mail");
|
|
$email = (new Email())
|
|
->from($email_data['from'])
|
|
->to(...$email_data['to'])
|
|
->subject($email_data['subject'])
|
|
->html($email_data['body']);
|
|
|
|
// Add CCs if present
|
|
if (!empty($email_data['cc'])) {
|
|
$email->cc(...$email_data['cc']);
|
|
}
|
|
|
|
// Attempt to send the email
|
|
// log_to_file("send_message - Trying to send");
|
|
try {
|
|
$mailer->send($email);
|
|
log_to_file("send_message - Successfully sent SMTP mail from ", $email_data['from']);
|
|
return true; // Email sent successfully
|
|
} catch (TransportExceptionInterface $e) {
|
|
error_log('Error sending email: ' . $e->getMessage());
|
|
return false; // Sending failed
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Reply to an email message.
|
|
*
|
|
* @param array $message The message details.
|
|
* @return bool True if the message is replied to successfully, false otherwise.
|
|
* @throws Exception If required fields are missing or an error occurs.
|
|
*/
|
|
public static function reply_message($message) {
|
|
// Prepare email data and connection info
|
|
$email_data = self::prepare_email_data($message);
|
|
// log_to_file("reply_message - Email Data: ", $email_data);
|
|
|
|
// Extract connection info
|
|
$connection_info = $email_data['connection_info'];
|
|
|
|
// Validate required fields
|
|
if (empty($email_data['to']) || empty($email_data['from']) || empty($email_data['subject']) || empty($email_data['body'])) {
|
|
throw new Exception(__('Missing required fields for replying to the email.', 'rl-mailwarmer'));
|
|
}
|
|
|
|
// Attempt to find the original email via IMAP
|
|
|
|
|
|
|
|
// // Attempt to find the original email and reply via IMAP
|
|
// try {
|
|
// $imap = new \PhpImap\Mailbox(
|
|
// sprintf('{%s:%d/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']),
|
|
// $connection_info['imap_username'],
|
|
// $connection_info['imap_password'],
|
|
// null,
|
|
// 'UTF-8'
|
|
// );
|
|
|
|
// // Search for the email with the matching subject
|
|
// $emails = $imap->searchMailbox('SUBJECT "' . addslashes($email_data['subject']) . '"');
|
|
// if (!empty($emails)) {
|
|
// // Fetch the email data
|
|
// $original_email = $imap->getMail($emails[0]);
|
|
|
|
// // Prepare and send the reply via IMAP
|
|
// $imap->reply(
|
|
// $emails[0],
|
|
// $email_data['body'],
|
|
// ['from' => $email_data['from'], 'cc' => $email_data['cc']]
|
|
// );
|
|
|
|
// return true; // Reply sent successfully via IMAP
|
|
// }
|
|
|
|
|
|
|
|
|
|
try {
|
|
// log_to_file("reply_message - Trying to reply via IMAP");
|
|
$imap = new \PhpImap\Mailbox(
|
|
sprintf('{%s:%d/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']),
|
|
$connection_info['username'],
|
|
$connection_info['mail_password'],
|
|
null,
|
|
'UTF-8'
|
|
);
|
|
|
|
// Search for the email with the matching subject
|
|
// log_to_file("reply_message - Searching for message");
|
|
$emails = $imap->searchMailbox('SUBJECT "' . addslashes($email_data['subject']) . '"');
|
|
if (!empty($emails)) {
|
|
|
|
|
|
// Fetch the email data
|
|
$original_email = $imap->getMail($emails[0]);
|
|
// log_to_file("reply_message - Message found!");
|
|
// log_to_file("reply_message - IMAP Message: ", $original_email);
|
|
|
|
// Step 2: Send the reply via SMTP
|
|
$transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport(
|
|
$connection_info['smtp_server'],
|
|
$connection_info['smtp_port']
|
|
);
|
|
|
|
// Set authentication details
|
|
$transport->setUsername($connection_info['username']);
|
|
$transport->setPassword($connection_info['mail_password']);
|
|
|
|
$mailer = new Mailer($transport);
|
|
|
|
$reply_email = (new Email())
|
|
->from($email_data['from'])
|
|
->to(...$email_data['to'])
|
|
->subject('Re: ' . $original_email->subject)
|
|
->html($email_data['body']);
|
|
|
|
// Add headers for threading
|
|
$headers = $reply_email->getHeaders();
|
|
$headers->addTextHeader('In-Reply-To', $original_email->messageId);
|
|
$headers->addTextHeader('References', trim($original_email->headers->references . ' ' . $original_email->messageId));
|
|
|
|
// ->addHeader('In-Reply-To', $original_email->messageId)
|
|
// ->addHeader('References', trim($original_email->headers->references . ' ' . $original_email->messageId));
|
|
|
|
if (!empty($email_data['cc'])) {
|
|
$reply_email->cc(...$email_data['cc']);
|
|
}
|
|
|
|
$mailer->send($reply_email);
|
|
|
|
log_to_file("reply_message - Successfully sent IMAP/SMTP reply from ", $email_data['from']);
|
|
|
|
// Step 3: Upload the reply to the Sent folder
|
|
$imap_stream = imap_open(
|
|
sprintf('{%s:%d/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']),
|
|
$connection_info['username'],
|
|
$connection_info['mail_password']
|
|
);
|
|
|
|
$raw_message = $reply_email->toString(); // Convert the Email object to raw MIME format
|
|
imap_append($imap_stream, sprintf('{%s}/Sent', $connection_info['imap_server']), $raw_message);
|
|
|
|
imap_close($imap_stream);
|
|
|
|
// Create the reply headers
|
|
// $reply_headers = [
|
|
// 'In-Reply-To' => $original_email->messageId,
|
|
// 'References' => trim($original_email->headers->references . ' ' . $original_email->messageId),
|
|
// ];
|
|
|
|
// // Construct the reply body
|
|
// $reply_body = $email_data['body'] . "\n\n" .
|
|
// 'On ' . $original_email->date . ', ' . $original_email->fromName . ' <' . $original_email->fromAddress . '> wrote:' . "\n" .
|
|
// $original_email->textPlain;
|
|
|
|
// // Send the reply via IMAP
|
|
// log_to_file("reply_message - Sending message via IMAP");
|
|
// $imap->addMessageToSentFolder(
|
|
// 'To: ' . implode(', ', $email_data['to']) . "\r\n" .
|
|
// 'Cc: ' . implode(', ', $email_data['cc']) . "\r\n" .
|
|
// 'Subject: Re: ' . $original_email->subject . "\r\n" .
|
|
// 'From: ' . $email_data['from'] . "\r\n" .
|
|
// 'In-Reply-To: ' . $reply_headers['In-Reply-To'] . "\r\n" .
|
|
// 'References: ' . $reply_headers['References'] . "\r\n" .
|
|
// "\r\n" .
|
|
// $reply_body
|
|
// );
|
|
// log_to_file("reply_message - Done message via IMAP");
|
|
|
|
// $mailer = new Mailer($transport);
|
|
// $mailer->send($reply);
|
|
|
|
return true; // Reply sent successfully
|
|
}
|
|
} catch (Exception $e) {
|
|
log_to_file('IMAP Error: ' . $e->getMessage());
|
|
}
|
|
|
|
// Fallback to SMTP if IMAP fails
|
|
try {
|
|
// log_to_file("reply_message - Falling back to SMTP");
|
|
$smtp_reply = (new Email())
|
|
->from($email_data['from'])
|
|
->to(...$email_data['to'])
|
|
->subject($email_data['subject'])
|
|
->html($email_data['body']);
|
|
|
|
// Add CCs if present
|
|
if (!empty($email_data['cc'])) {
|
|
$smtp_reply->cc(...$email_data['cc']);
|
|
}
|
|
|
|
// Create the SMTP transport
|
|
$transport = new Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport(
|
|
$connection_info['smtp_server'],
|
|
$connection_info['smtp_port']
|
|
);
|
|
|
|
// Set authentication details
|
|
$transport->setUsername($connection_info['username']);
|
|
$transport->setPassword($connection_info['mail_password']);
|
|
|
|
// Create the mailer
|
|
$mailer = new Symfony\Component\Mailer\Mailer($transport);
|
|
$mailer->send($smtp_reply);
|
|
log_to_file("reply_message - Sent reply via fallback SMTP from ", $email_data['from']);
|
|
// log_to_file('reply_message - SMTP Send Success (?)');
|
|
|
|
return true; // Fallback SMTP reply sent successfully
|
|
} catch (Exception $e) {
|
|
log_to_file('reply_message - SMTP Error: ' . $e->getMessage());
|
|
return false; // Reply failed
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a metabox to the WP dashboard for monitoring and processing the message queue.
|
|
*/
|
|
add_action('wp_dashboard_setup', function () {
|
|
wp_add_dashboard_widget(
|
|
'rl_mailwarmer_message_queue',
|
|
__('Message Queue', 'rl-mailwarmer'),
|
|
'rl_mailwarmer_render_message_queue_widget'
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Render the Message Queue dashboard widget.
|
|
*/
|
|
function rl_mailwarmer_render_message_queue_widget() {
|
|
global $wpdb;
|
|
|
|
// Count past-due messages
|
|
$table_name = $wpdb->prefix . 'rl_mailwarmer_messages';
|
|
$past_due_count = $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
"SELECT COUNT(*) FROM $table_name WHERE status = %s AND scheduled_for_timestamp < %s",
|
|
'pending',
|
|
current_time('mysql')
|
|
)
|
|
);
|
|
|
|
?>
|
|
<div id="rl-mailwarmer-queue">
|
|
<p><strong><?php esc_html_e('Past-Due Messages:', 'rl-mailwarmer'); ?></strong> <?php echo esc_html($past_due_count); ?></p>
|
|
<button id="process-message-queue" class="button button-primary">
|
|
<?php esc_html_e('Process Message Queue', 'rl-mailwarmer'); ?>
|
|
</button>
|
|
<div id="message-queue-result" style="margin-top: 10px;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const button = document.getElementById('process-message-queue');
|
|
const resultDiv = document.getElementById('message-queue-result');
|
|
|
|
button.addEventListener('click', function () {
|
|
button.disabled = true;
|
|
resultDiv.textContent = '<?php esc_html_e('Processing...', 'rl-mailwarmer'); ?>';
|
|
|
|
jQuery.post(ajaxurl, {
|
|
action: 'rl_mailwarmer_process_message_queue',
|
|
security: '<?php echo esc_js(wp_create_nonce('rl_mailwarmer_queue_nonce')); ?>'
|
|
}, function (response) {
|
|
button.disabled = false;
|
|
|
|
if (response.success) {
|
|
console.log("Success");
|
|
resultDiv.textContent = '<?php esc_html_e('Messages processed:', 'rl-mailwarmer'); ?> ' + response.data.processed_count;
|
|
} else {
|
|
console.log("Failure");
|
|
resultDiv.textContent = '<?php esc_html_e('Error:', 'rl-mailwarmer'); ?> ' + response.data.message;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* AJAX handler to process the message queue.
|
|
*/
|
|
add_action('wp_ajax_rl_mailwarmer_process_message_queue', function () {
|
|
// log_to_file("wp_ajax_rl_mailwarmer_process_message_queue - Running");
|
|
// Verify nonce
|
|
if (!check_ajax_referer('rl_mailwarmer_queue_nonce', 'security', false)) {
|
|
wp_send_json_error(['message' => __('Invalid nonce.', 'rl-mailwarmer')]);
|
|
}
|
|
|
|
// Ensure the user has permission
|
|
if (!current_user_can('manage_options')) {
|
|
wp_send_json_error(['message' => __('Permission denied.', 'rl-mailwarmer')]);
|
|
}
|
|
|
|
try {
|
|
// Process pending messages
|
|
// log_to_file("wp_ajax_rl_mailwarmer_process_message_queue - Trying process_pending_messages()");
|
|
$processed_count = RL_MailWarmer_Message_Handler::process_pending_messages();
|
|
|
|
wp_send_json_success(['processed_count' => $processed_count]);
|
|
} catch (Exception $e) {
|
|
wp_send_json_error(['message' => $e->getMessage()]);
|
|
}
|
|
});
|