Compare commits
8 commits
warmup-acc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| dd22cf8777 | |||
| 2c104022df | |||
| e251e7fe24 | |||
| c11bc80f75 | |||
| 2f0f177738 | |||
| 5e92999bd2 | |||
| de40085318 | |||
| cc6dc646b2 |
27 changed files with 9808 additions and 874 deletions
19
CLAUDE.md
Normal file
19
CLAUDE.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# RL MailWarmer WordPress Plugin Guide
|
||||
|
||||
## Build/Test Commands
|
||||
- **Run PHPUnit Tests**: `vendor/bin/phpunit tests`
|
||||
- **Run Single Test**: `vendor/bin/phpunit tests/TestName.php`
|
||||
- **Composer Update**: `composer update`
|
||||
- **Composer Install**: `composer install`
|
||||
|
||||
## Code Style Guidelines
|
||||
- **Class Naming**: Use `RL_MailWarmer_ClassName` format with underscores
|
||||
- **Method Naming**: Use snake_case for method names
|
||||
- **File Naming**: Use `class-rl-mailwarmer-name.php` for class files
|
||||
- **Database**: Use `rl_mailwarmer_` prefix for all tables
|
||||
- **Error Handling**: Use WP_Error for WordPress-specific errors, Exceptions for PHP-level errors
|
||||
- **Validation**: Always validate and sanitize inputs using WordPress functions
|
||||
- **Documentation**: Use PHPDoc blocks for classes and methods
|
||||
- **Security**: Follow WordPress security best practices (prepare SQL, validate nonces)
|
||||
- **Imports**: Place includes at the top, use require_once for class dependencies
|
||||
- **Type Hints**: Use PHP type hints for method parameters and return types
|
||||
19
composer.json
Normal file
19
composer.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"require": {
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"symfony/mailer": "^7.2",
|
||||
"symfony/http-client": "^7.2",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
"openai-php/client": "^0.10.3",
|
||||
"php-imap/php-imap": "^5.0",
|
||||
"symfony/mime": "^7.2"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"php-http/discovery": true
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^11.5"
|
||||
}
|
||||
}
|
||||
3989
composer.lock
generated
Normal file
3989
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
344
css/modal.css
Normal file
344
css/modal.css
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
/* Modal Styles */
|
||||
.mf-floating-action-button {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: #0073aa;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
transition: transform 0.3s, background-color 0.3s;
|
||||
}
|
||||
|
||||
.mf-floating-action-button:hover {
|
||||
transform: scale(1.1);
|
||||
background-color: #005d87;
|
||||
}
|
||||
|
||||
.mf-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
}
|
||||
|
||||
.mf-modal-overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.mf-modal-container {
|
||||
background: white;
|
||||
width: 90%;
|
||||
max-width: 700px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mf-modal-header {
|
||||
padding: 20px;
|
||||
background-color: #f8f8f8;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mf-modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.4rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mf-modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.mf-modal-close:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mf-modal-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mf-modal-footer {
|
||||
padding: 15px 20px;
|
||||
background-color: #f8f8f8;
|
||||
border-top: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mf-modal-footer button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mf-back-btn {
|
||||
background-color: #f0f0f0;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.mf-next-btn {
|
||||
background-color: #0073aa;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mf-back-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.mf-next-btn:hover {
|
||||
background-color: #005d87;
|
||||
}
|
||||
|
||||
.mf-next-btn.disabled {
|
||||
background-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Step indicator styles */
|
||||
.mf-steps-indicator {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.mf-step-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #ddd;
|
||||
margin: 0 5px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.mf-step-dot.active {
|
||||
background-color: #0073aa;
|
||||
}
|
||||
|
||||
.mf-step-dot.completed {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
|
||||
/* Multi-step form styles */
|
||||
.mf-step {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mf-step.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mf-step h3 {
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.mf-form-field {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mf-form-field label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mf-form-field input,
|
||||
.mf-form-field select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mf-form-field input:focus,
|
||||
.mf-form-field select:focus {
|
||||
border-color: #0073aa;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1px #0073aa;
|
||||
}
|
||||
|
||||
.mf-selection-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mf-selection-button {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background-color: #f8f8f8;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.mf-selection-button:hover {
|
||||
border-color: #0073aa;
|
||||
background-color: #f0f7fb;
|
||||
}
|
||||
|
||||
.mf-selection-button.selected {
|
||||
border-color: #0073aa;
|
||||
background-color: #e6f3fa;
|
||||
}
|
||||
|
||||
.mf-selection-button i {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.mf-selection-button.selected i {
|
||||
color: #0073aa;
|
||||
}
|
||||
|
||||
.mf-validation-error {
|
||||
color: #d32f2f;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mf-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.mf-loading-spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #0073aa;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.mf-success-message,
|
||||
.mf-error-message {
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mf-success-message {
|
||||
background-color: #e8f5e9;
|
||||
border: 1px solid #c8e6c9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.mf-error-message {
|
||||
background-color: #ffebee;
|
||||
border: 1px solid #ffcdd2;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.mf-mx-option {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.mf-mx-option:hover {
|
||||
border-color: #0073aa;
|
||||
background-color: #f0f7fb;
|
||||
}
|
||||
|
||||
.mf-mx-option.selected {
|
||||
border-color: #0073aa;
|
||||
background-color: #e6f3fa;
|
||||
}
|
||||
|
||||
.mf-email-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.mf-email-row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mf-email-row input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mf-add-email-btn {
|
||||
background-color: #0073aa;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mf-add-email-btn:hover {
|
||||
background-color: #005d87;
|
||||
}
|
||||
|
||||
.mf-remove-email-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mf-remove-email-btn:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
1305
includes/class-rl-mailwarmer-conversation-helper.php
Normal file
1305
includes/class-rl-mailwarmer-conversation-helper.php
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -19,15 +19,18 @@ class RL_MailWarmer_DB_Helper {
|
|||
$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)
|
||||
KEY first_message_timestamp_idx (first_message_timestamp),
|
||||
INDEX email_account_id_idx (email_account_id)
|
||||
) $charset_collate;";
|
||||
|
||||
// Message table
|
||||
|
|
@ -35,6 +38,7 @@ class RL_MailWarmer_DB_Helper {
|
|||
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,
|
||||
|
|
@ -45,7 +49,8 @@ class RL_MailWarmer_DB_Helper {
|
|||
PRIMARY KEY (id),
|
||||
INDEX scheduled_idx (scheduled_for_timestamp, status),
|
||||
INDEX conversation_id_idx (conversation_id),
|
||||
INDEX campaign_id_idx (campaign_id)
|
||||
INDEX campaign_id_idx (campaign_id),
|
||||
INDEX email_account_id_idx (email_account_id)
|
||||
) $charset_collate;";
|
||||
|
||||
// Backup table
|
||||
|
|
@ -85,21 +90,37 @@ class RL_MailWarmer_DB_Helper {
|
|||
*/
|
||||
public static function insert_conversation($conversation_data) {
|
||||
global $wpdb;
|
||||
|
||||
// $wpdb->insert(
|
||||
// "{$wpdb->prefix}" . self::$conversations_table,
|
||||
// [
|
||||
// 'campaign_id' => $campaign_id,
|
||||
// 'conversation_steps' => json_encode($conversation_steps),
|
||||
// 'prompt' => $prompt,
|
||||
// ],
|
||||
// ['%d', '%s', '%s']
|
||||
// );
|
||||
$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.
|
||||
*/
|
||||
|
|
@ -129,6 +150,99 @@ class RL_MailWarmer_DB_Helper {
|
|||
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.
|
||||
|
|
|
|||
|
|
@ -115,20 +115,30 @@ class RL_MailWarmer_Domain_Helper {
|
|||
}
|
||||
|
||||
$domain_name = $domain_post->post_title;
|
||||
$credentials = self::get_cloudflare_credentials($domain_post);
|
||||
try {
|
||||
$credentials = self::get_cloudflare_credentials($domain_post);
|
||||
|
||||
} catch (Exception $e) {
|
||||
throw new Exception(__('get_cloudflare_client - Failed to find CloudFlare zone: ', 'rl-mailwarmer') . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$client = new \GuzzleHttp\Client([
|
||||
'base_uri' => 'https://api.cloudflare.com/client/v4/',
|
||||
'domain' => $domain_name,
|
||||
'api_email' => $credentials['api_email'],
|
||||
'api_key' => $credentials['api_key'],
|
||||
'zone_id' => $credentials['zone_id'],
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
]);
|
||||
return $client;
|
||||
if ($credentials) {
|
||||
$client = new \GuzzleHttp\Client([
|
||||
'base_uri' => 'https://api.cloudflare.com/client/v4/',
|
||||
'domain' => $domain_name,
|
||||
'api_email' => $credentials['api_email'],
|
||||
'api_key' => $credentials['api_key'],
|
||||
'zone_id' => $credentials['zone_id'],
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
]);
|
||||
return $client;
|
||||
} else {
|
||||
log_to_file("get_cloudflare_client - Unable to get cloudflare client for $domain_post->post_title");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -326,18 +336,36 @@ class RL_MailWarmer_Domain_Helper {
|
|||
|
||||
$client = self::get_cloudflare_client($domain);
|
||||
// $zone_id = self::get_cloudflare_zone_id($client, $credentials, $domain_post->post_title);
|
||||
$dns_records = self::fetch_dns_records($client);
|
||||
// log_to_file("generate_domain_report - All Records: ", $dns_records);
|
||||
|
||||
$report = [
|
||||
'domain_health' => self::check_domain_registration($domain_name),
|
||||
'a_record' => self::check_a_record($dns_records),
|
||||
'mx_record' => self::check_mx_record($dns_records),
|
||||
'spf_record' => self::check_spf_record($dns_records),
|
||||
'dkim_records' => self::check_dkim_record($dns_records),
|
||||
'dmarc_record' => self::check_dmarc_record($dns_records),
|
||||
'blacklists' => self::check_blacklists($domain_name),
|
||||
];
|
||||
if ($client) {
|
||||
$dns_records = self::fetch_dns_records($client);
|
||||
log_to_file("generate_domain_report - All Records: ", $dns_records);
|
||||
|
||||
|
||||
|
||||
if (isset($dns_records[0])) {
|
||||
$report = [
|
||||
'domain_health' => self::check_domain_registration($domain_name),
|
||||
'a_record' => self::check_a_record($dns_records),
|
||||
'mx_record' => self::check_mx_record($dns_records),
|
||||
'spf_record' => self::check_spf_record($dns_records),
|
||||
'dkim_records' => self::check_dkim_record($dns_records),
|
||||
'dmarc_record' => self::check_dmarc_record($dns_records),
|
||||
'blacklists' => self::check_blacklists($domain_name),
|
||||
];
|
||||
} else {
|
||||
$report = [
|
||||
'domain_health' => self::check_domain_registration($domain_name),
|
||||
'blacklists' => self::check_blacklists($domain_name),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
log_to_file("generate_domain_report - Unable to connect to CloudFlare for $domain_name");
|
||||
$report = [
|
||||
'domain_health' => self::check_domain_registration($domain_name),
|
||||
'blacklists' => self::check_blacklists($domain_name),
|
||||
];
|
||||
}
|
||||
|
||||
// log_to_file("generate_domain_report - Health Report for $domain_name: ", $report);
|
||||
|
||||
|
|
@ -439,13 +467,13 @@ class RL_MailWarmer_Domain_Helper {
|
|||
*/
|
||||
private static function check_a_record($dns_records)
|
||||
{
|
||||
$domain_name = $dns_records[0]['zone_name'];
|
||||
$domain_name = $dns_records[0]['name'];
|
||||
// log_to_file("check_a_record - Running check_mx_record for $domain_name");
|
||||
|
||||
foreach ($dns_records as $record) {
|
||||
// Check if the record matches the criteria
|
||||
// log_to_file("check_a_record - DNS Record: ", $record);
|
||||
if ( ($record['zone_name'] === $record['name']) && ($record['type'] === 'A') ) {
|
||||
if ( ($record['name'] === $domain_name) && ($record['type'] === 'A') ) {
|
||||
$ip = $record['content'];
|
||||
$http_status = self::get_http_status($domain_name);
|
||||
|
||||
|
|
@ -501,12 +529,12 @@ class RL_MailWarmer_Domain_Helper {
|
|||
*/
|
||||
private static function check_mx_record($dns_records)
|
||||
{
|
||||
$domain_name = $dns_records[0]['zone_name'];
|
||||
$domain_name = $dns_records[0]['name'];
|
||||
// log_to_file("check_mx_record - Running check_mx_record for $domain_name");
|
||||
|
||||
foreach ($dns_records as $record) {
|
||||
// Check if the record matches the criteria
|
||||
if ( ($record['zone_name'] === $domain_name) && ($record['type'] === 'MX') ) {
|
||||
if ( ($record['name'] === $domain_name) && ($record['type'] === 'MX') ) {
|
||||
$host = $record['content'];
|
||||
$ptr_record = gethostbyaddr(gethostbyname($host));
|
||||
|
||||
|
|
@ -546,7 +574,7 @@ class RL_MailWarmer_Domain_Helper {
|
|||
log_to_file("update_mx_record - Searching for existing record");
|
||||
// throw new Exception('Failed to fetch existing DNS records from CloudFlare.');
|
||||
|
||||
foreach ($dns_records['result'] as $record) {
|
||||
foreach ($dns_records as $record) {
|
||||
if ($record['content'] === $content && $record['priority'] === $priority) {
|
||||
$existing_record_id = $record['id'];
|
||||
log_to_file("update_mx_record - Matching record found");
|
||||
|
|
@ -579,14 +607,14 @@ class RL_MailWarmer_Domain_Helper {
|
|||
|
||||
log_to_file("update_mx_record - Attempting to update record");
|
||||
$response = self::update_dns_record($domain_post->ID, $domain_post->post_title, 'MX', $domain_post->post_title, $content, $ttl, $priority);
|
||||
$result = json_decode($response->getBody(), true);
|
||||
// $result = json_decode($response->getBody(), true);
|
||||
log_to_file("update_mx_record - Result: ", $result);
|
||||
|
||||
if (!$result['success']) {
|
||||
throw new Exception('Failed to update or create the MX record in CloudFlare.');
|
||||
}
|
||||
// if (!$result['success']) {
|
||||
// throw new Exception('Failed to update or create the MX record in CloudFlare.');
|
||||
// }
|
||||
|
||||
return $result; // Return the CloudFlare response
|
||||
return $response; // Return the CloudFlare response
|
||||
} catch (Exception $e) {
|
||||
log_to_file('update_mx_record - Error in update_mx_record: ' . $e->getMessage());
|
||||
return 'Error: ' . $e->getMessage();
|
||||
|
|
@ -602,7 +630,7 @@ class RL_MailWarmer_Domain_Helper {
|
|||
*/
|
||||
private static function check_spf_record($dns_records)
|
||||
{
|
||||
$domain_name = $dns_records[0]['zone_name'];
|
||||
$domain_name = $dns_records[0]['name'];
|
||||
// log_to_file("check_spf_record - Running check_spf_record for $domain_name");
|
||||
|
||||
foreach ($dns_records as $record) {
|
||||
|
|
@ -705,7 +733,7 @@ class RL_MailWarmer_Domain_Helper {
|
|||
*/
|
||||
private static function check_dmarc_record($dns_records)
|
||||
{
|
||||
$domain_name = $dns_records[0]['zone_name'];
|
||||
$domain_name = $dns_records[0]['name'];
|
||||
// log_to_file("check_dmarc_record - Running check_dmarc_record for $domain_name");
|
||||
|
||||
foreach ($dns_records as $record) {
|
||||
|
|
@ -821,8 +849,7 @@ class RL_MailWarmer_Domain_Helper {
|
|||
'TXT',
|
||||
$name,
|
||||
$wrapped_content,
|
||||
$existing_record['ttl'],
|
||||
$credentials
|
||||
$existing_record['ttl']
|
||||
);
|
||||
} else {
|
||||
return self::update_dns_record(
|
||||
|
|
@ -832,7 +859,6 @@ class RL_MailWarmer_Domain_Helper {
|
|||
$name,
|
||||
$wrapped_content,
|
||||
3600, // Default TTL
|
||||
$credentials
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -848,7 +874,7 @@ class RL_MailWarmer_Domain_Helper {
|
|||
*/
|
||||
private static function check_dkim_record($dns_records, $selectors = [])
|
||||
{
|
||||
$domain_name = $dns_records[0]['zone_name'];
|
||||
$domain_name = $dns_records[0]['name'];
|
||||
// log_to_file("check_dkim_record - Running check_dkim_record for $domain_name");
|
||||
|
||||
$dkim_records = [];
|
||||
|
|
@ -1072,7 +1098,28 @@ class RL_MailWarmer_Domain_Helper {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$results['dkim'] = 'No servers selected for the domain.';
|
||||
log_to_file("fix_deliverability_dns_issues - No servers selected for the domain. Choosing system default.");
|
||||
$server = get_field('defaut_mailferno_mx', 'option');
|
||||
$server_id = $server->ID;
|
||||
$selector = get_field('dkim_selector', $server_id);
|
||||
$value = get_field('dkim_value', $server_id);
|
||||
if ($selector && $value) {
|
||||
$dkim_record = $findRecord($dns_records, "{$selector}._domainkey", true);
|
||||
if (!$dkim_record) {
|
||||
try {
|
||||
$dkim_result = self::update_dkim_record($domain_post, $selector, 'add', $value);
|
||||
$results['dkim'][$selector] = $dkim_result
|
||||
? "DKIM record for selector '{$selector}' added successfully."
|
||||
: "Failed to add DKIM record for selector '{$selector}'.";
|
||||
} catch (Exception $e) {
|
||||
$results['dkim'][$selector] = 'Error: ' . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$results['dkim'][$selector] = "DKIM record for selector '{$selector}' already exists.";
|
||||
}
|
||||
} else {
|
||||
$results['dkim'][$server_id] = 'Missing DKIM selector or value for server: ' . $server_id;
|
||||
}
|
||||
}
|
||||
|
||||
// DMARC
|
||||
|
|
|
|||
637
includes/class-rl-mailwarmer-email-account-helper.php
Normal file
637
includes/class-rl-mailwarmer-email-account-helper.php
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
<?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')]);
|
||||
});
|
||||
|
||||
534
includes/class-rl-mailwarmer-message-handler.php
Normal file
534
includes/class-rl-mailwarmer-message-handler.php
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Helper functions for the conversation messages
|
||||
*/
|
||||
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mailer\Transport\Transport;
|
||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
|
||||
use PhpImap\Mailbox;
|
||||
use phpseclib3\Net\SSH2;
|
||||
use phpseclib3\Crypt\PublicKeyLoader;
|
||||
|
||||
|
||||
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;
|
||||
$results = [
|
||||
'success' => 0,
|
||||
'failure' => 0,
|
||||
];
|
||||
|
||||
// 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 10",
|
||||
'scheduled',
|
||||
current_time('mysql')
|
||||
),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
// if (empty($messages)) {
|
||||
// // log_to_file("process_pending_messages - messages empty");
|
||||
// return;
|
||||
// }
|
||||
|
||||
$messages_count = count($messages);
|
||||
if ($messages_count > 0) {
|
||||
action_log("process_pending_messages - Processing {$messages_count} messages" );
|
||||
|
||||
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);
|
||||
// }
|
||||
log_to_file("process_pending_messages - trying send_message for {$message['id']} from {$message['from_email']} to ", $message['to_email']);
|
||||
$result = self::send_message($message);
|
||||
|
||||
// Update message status to 'sent' on success
|
||||
if ($result) {
|
||||
self::update_message_status($message['id'], 'sent');
|
||||
$results['success']++;
|
||||
log_to_file("process_pending_messages - Success sending message: {$message['id']}");
|
||||
action_log("process_pending_messages - Sent email {$message['id']} from {$message['from_email']} to ", $message['to_email']);
|
||||
} else {
|
||||
self::update_message_status($message['id'], 'failed');
|
||||
log_to_file("process_pending_messages - Error sending message: {$message['id']}");
|
||||
$results['failure']++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// Handle errors gracefully and log them
|
||||
log_to_file('process_pending_messages - Error processing message ID ' . $message['id'] . ': ' . $e->getMessage());
|
||||
self::update_message_status($message['id'], 'failed');
|
||||
$results['failure']++;
|
||||
}
|
||||
// sleep(3);
|
||||
}
|
||||
log_to_file("process_pending_messages - Results: ", $results);
|
||||
action_log("process_pending_messages - Finished processing {$messages_count} messages with {$results['success']} sent and {$results['failure']} failures");
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
if (!empty($connection_info['smtp_password'])) {
|
||||
$password = $connection_info['smtp_password'];
|
||||
} else {
|
||||
$password = $connection_info['mail_password'];
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
if (empty($email_data['to']) || empty($email_data['from']) || empty($email_data['subject']) || empty($email_data['text_body'])) {
|
||||
|
||||
// log_to_file("send_message - Missing required fields for sending the email");
|
||||
throw new Exception(__('Missing required fields for sending the email.', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
// Create the SMTP transport
|
||||
try {
|
||||
// log_to_file("send_message - Creating Transport");
|
||||
// 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($email_data['from']);
|
||||
$transport->setPassword($password);
|
||||
|
||||
$to_addresses = $email_data['to'];
|
||||
if (!is_array($to_addresses)) {
|
||||
$to_addresses = json_decode($to_addresses, true);
|
||||
}
|
||||
$to_addresses_type = gettype($to_addresses);
|
||||
// log_to_file("send_message - To ({$to_addresses_type}): ", $to_addresses);
|
||||
|
||||
// Create the mailer
|
||||
$mailer = new Symfony\Component\Mailer\Mailer($transport);
|
||||
|
||||
// Send an email
|
||||
|
||||
$email_message = (new Symfony\Component\Mime\Email())
|
||||
->from(new Address($email_data['from'], $email_data['name']))
|
||||
->to(...$to_addresses)
|
||||
->subject($email_data['subject'])
|
||||
->text($email_data['text_body'])
|
||||
->html($email_data['html_body']);
|
||||
|
||||
// Add headers
|
||||
$campaign_tracking_id = $email_data['campaign_tracking_id'];
|
||||
// $previous_message_id = $message['previous_message_id'];
|
||||
// if ($previous_message_id) {
|
||||
// $campaign_tracking_id .= '-' . $previous_message_id;
|
||||
// }
|
||||
$email_message->getHeaders()->addTextHeader('X-MFTID', $campaign_tracking_id);
|
||||
// log_to_file("send_message - Creating email with MFTID: {$campaign_tracking_id}");
|
||||
|
||||
// log_to_file("send_message - Trying to send email.");
|
||||
$smtp_result = $mailer->send($email_message);
|
||||
|
||||
// log_to_file("send_message - Message sent!", $smtp_result);
|
||||
return true;
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
log_to_file("send_message - Error sending email {$message['id']}: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function search_email_by_x_mftid($imap_stream, $imap_server, $campaign_tracking_id) {
|
||||
$folders = imap_list($imap_stream, $imap_server, '*');
|
||||
$result = ['folder' => null, 'email' => null];
|
||||
|
||||
$search_term = '/X\-MFTID: ' . preg_quote($campaign_tracking_id, '/') . '.+/i';
|
||||
log_to_file("search_email_by_x_mftid - search term: {$search_term}");
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
$decoded_folder = imap_utf7_decode($folder);
|
||||
// log_to_file("search_email_by_x_mftid - decoded_folder: ", $decoded_folder);
|
||||
|
||||
$status = imap_status($imap_stream, $decoded_folder, SA_MESSAGES);
|
||||
if ($status->messages > 0) {
|
||||
log_to_file("search_email_by_x_mftid - Searching {$decoded_folder}");
|
||||
$emails = imap_search($imap_stream, 'TEXT "' . $campaign_tracking_id . '"', SE_UID);
|
||||
if ($emails) {
|
||||
$result['folder'] = $decoded_folder;
|
||||
$result['email'] = $emails;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
$full_name = get_post_meta($from_post_id, 'full_name', true);
|
||||
$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 = json_decode();
|
||||
$to_emails = is_array($message['to_email']) ? $message['to_email'] : json_decode($message['to_email']);
|
||||
$cc_emails = is_array($message['cc']) ? $message['cc'] : json_decode($message['cc']);
|
||||
|
||||
$campaign_tracking_id = $message['campaign_tracking_id'] . '-' . $message['id'];
|
||||
$previous_message_id = $message['previous_message_id'];
|
||||
$text_body = $message['body'] . "\n\n" . $campaign_tracking_id;
|
||||
$html_body = $message['body'] . "<br /><br /><span style='font-size:0.5rem;'>{$campaign_tracking_id}</span>";
|
||||
|
||||
return [
|
||||
'id' => $message['id'],
|
||||
'connection_info' => $connection_info,
|
||||
'to' => array_filter(array_map('trim', array_map('stripslashes', $to_emails))),
|
||||
'cc' => array_filter(array_map('trim', array_map('stripslashes', $cc_emails))),
|
||||
'subject' => $message['subject'],
|
||||
'text_body' => $text_body,
|
||||
'html_body' => $html_body,
|
||||
'from' => $message['from_email'],
|
||||
'name' => $full_name,
|
||||
'campaign_tracking_id' => $campaign_tracking_id,
|
||||
'previous_message_id' => $previous_message_id,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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']
|
||||
);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 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);
|
||||
|
||||
// // Validate required fields
|
||||
// if (empty($email_data['to']) || empty($email_data['from']) || empty($email_data['subject']) || empty($email_data['text_body'])) {
|
||||
// throw new Exception(__('Missing required fields for replying to the email.', 'rl-mailwarmer'));
|
||||
// }
|
||||
|
||||
// // Extract connection info
|
||||
// $connection_info = $email_data['connection_info'];
|
||||
// $emails = '';
|
||||
|
||||
|
||||
// try {
|
||||
// // log_to_file("reply_message - Trying to reply via IMAP {$connection_info['imap_server']}:{$connection_info['imap_port']}");
|
||||
// // $imap = new \PhpImap\Mailbox(
|
||||
// // sprintf('{%s:%s/imap/ssl}', $connection_info['imap_server'], $connection_info['imap_port']),
|
||||
// // $connection_info['username'],
|
||||
// // $connection_info['mail_password'],
|
||||
// // null,
|
||||
// // 'UTF-8'
|
||||
// // );
|
||||
|
||||
// // $imap->checkMailbox();
|
||||
|
||||
// // // Search for the email with the matching subject
|
||||
// // log_to_file("reply_message - Searching for message with X-MFTID");
|
||||
// // $emails = $imap->searchMailbox('HEADER X-MFTID "' . addslashes($email_data['campaign_tracking_id']) . '"');
|
||||
|
||||
|
||||
|
||||
// $password = $connection_info['mail_password'];
|
||||
// $email = $connection_info['username'];
|
||||
|
||||
// log_to_file("reply_message - Trying to reply via IMAP for {$email} using: ", $connection_info);
|
||||
|
||||
// $imap_server = '{' . $connection_info['imap_server'] . ':' . $connection_info['imap_port'] . '/imap/ssl}';
|
||||
|
||||
// log_to_file("reply_message - 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("reply_message - IMAP stream opened.");
|
||||
// $imap_search_result = self::search_email_by_x_mftid($imap_stream, $imap_server, $email_data['campaign_tracking_id']);
|
||||
// if ($imap_search_result['folder']) {
|
||||
// log_to_file("reply_message - Email with X-MFTID found in folder: {$imap_search_result['folder']}");
|
||||
// log_to_file("reply_message - Email: ", $imap_search_result['email']);
|
||||
// // $emails = $imap_search_result['email'];
|
||||
// // Continue with reply logic
|
||||
// } else {
|
||||
// log_to_file("No email found with X-MFTID: " . $email_data['campaign_tracking_id']);
|
||||
// }
|
||||
// imap_close($imap_stream);
|
||||
// }
|
||||
|
||||
|
||||
// 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']);
|
||||
// $to_addresses = $email_data['to'];
|
||||
// if (!is_array($to_addresses)) {
|
||||
// $to_addresses = json_decode($to_addresses, true);
|
||||
// }
|
||||
|
||||
// $mailer = new Mailer($transport);
|
||||
|
||||
// $reply_email = (new Email())
|
||||
// ->from(new Address($email_data['from'], $email_data['name']))
|
||||
// ->to(...$to_addresses)
|
||||
// ->subject('Re: ' . $original_email->subject)
|
||||
// ->text($email_data['text_body'])
|
||||
// ->html($email_data['html_body']);
|
||||
|
||||
// // Add headers
|
||||
// $campaign_tracking_id = $email_data['campaign_tracking_id'];
|
||||
// $reply_email->getHeaders()->addTextHeader('X-MFTID', $campaign_tracking_id);
|
||||
|
||||
// // 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
|
||||
// } else {
|
||||
// log_to_file("reply_message - Unable to reply via IMAP. Falling back to SMTP");
|
||||
// }
|
||||
// } catch (Exception $e) {
|
||||
// log_to_file('reply_message - IMAP Error: ' . $e->getMessage());
|
||||
// }
|
||||
|
||||
// // Fallback to SMTP if IMAP fails
|
||||
// try {
|
||||
// log_to_file("reply_message - Falling back to SMTP");
|
||||
|
||||
// $result = self::send_message($message);
|
||||
// return $result;
|
||||
// // $to_addresses = $email_data['to'];
|
||||
// // if (!is_array($to_addresses)) {
|
||||
// // $to_addresses = json_decode($to_addresses, true);
|
||||
// // }
|
||||
// // log_to_file("reply_message - Creating SMTP message");
|
||||
|
||||
// // $smtp_reply = (new Email())
|
||||
// // ->from(new Address($email_data['from'], $email_data['name']))
|
||||
// // ->to(...$to_addresses)
|
||||
// // ->subject($email_data['subject'])
|
||||
// // ->text($email_data['text_body'])
|
||||
// // ->html($email_data['html_body']);
|
||||
|
||||
// // // Add headers
|
||||
// // $campaign_tracking_id = $email_data['campaign_tracking_id'];
|
||||
// // $smtp_reply->getHeaders()->addTextHeader('X-MFTID', $campaign_tracking_id);
|
||||
|
||||
// // // Add CCs if present
|
||||
// // // if (!empty($email_data['cc'])) {
|
||||
// // // $smtp_reply->cc(...$email_data['cc']);
|
||||
// // // }
|
||||
|
||||
// // // Create the SMTP transport
|
||||
// // log_to_file("reply_message - Creating 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
|
||||
// // log_to_file("reply_message - Creating SMTP mailer");
|
||||
// // $mailer = new Symfony\Component\Mailer\Mailer($transport);
|
||||
// // $smtp_result = $mailer->send($smtp_reply);
|
||||
// // log_to_file("reply_message - Sent reply via fallback SMTP from {$email_data['from']}:", $smtp_result);
|
||||
// // // log_to_file('reply_message - SMTP Send Success (?)');
|
||||
|
||||
// // Fallback SMTP reply sent successfully
|
||||
// } catch (Exception $e) {
|
||||
// log_to_file('reply_message - SMTP Error: ' . $e->getMessage());
|
||||
// return false; // Reply failed
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
247
includes/class-rl-mailwarmer-message-helper.php
Normal file
247
includes/class-rl-mailwarmer-message-helper.php
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
|
||||
add_action('admin_menu', function () {
|
||||
add_menu_page(
|
||||
__('Messages', 'rl-mailwarmer'), // Page title
|
||||
__('Messages', 'rl-mailwarmer'), // Menu title
|
||||
'manage_options', // Capability
|
||||
'rl-mailwarmer-messages', // Menu slug
|
||||
'rl_mailwarmer_render_messages_page', // Callback function
|
||||
'dashicons-email-alt', // Icon
|
||||
9 // Position
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Render the Messages admin page with checkboxes and a Delete button.
|
||||
*/
|
||||
function rl_mailwarmer_render_messages_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$conversation_table = $wpdb->prefix . 'rl_mailwarmer_conversations';
|
||||
$message_table = $wpdb->prefix . 'rl_mailwarmer_messages';
|
||||
|
||||
// Fetch conversations with their messages
|
||||
$conversations = $wpdb->get_results("
|
||||
SELECT c.id as conversation_id, c.campaign_id, c.created_at, m.id as message_id, m.scheduled_for_timestamp, m.from_email, m.to_email, m.subject
|
||||
FROM $conversation_table c
|
||||
LEFT JOIN $message_table m ON c.id = m.conversation_id
|
||||
WHERE m.status != 'scheduled'
|
||||
ORDER BY c.created_at DESC, m.scheduled_for_timestamp ASC
|
||||
");
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e('Messages', 'rl-mailwarmer'); ?></h1>
|
||||
|
||||
<?php if (empty($conversations)) : ?>
|
||||
<p><?php esc_html_e('No messages found.', 'rl-mailwarmer'); ?></p>
|
||||
<?php else : ?>
|
||||
<form id="message-management-form" method="post">
|
||||
<table class="widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 50px;">
|
||||
<input type="checkbox" id="check-all-messages">
|
||||
</th>
|
||||
<th><?php esc_html_e('Conversation ID', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('Campaign ID', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('Created At', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('Message ID', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('Scheduled For', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('From', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('To', 'rl-mailwarmer'); ?></th>
|
||||
<th><?php esc_html_e('Subject', 'rl-mailwarmer'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($conversations as $conversation) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<?php if ($conversation->message_id) : ?>
|
||||
<input type="checkbox" name="selected_messages[]" value="<?php echo esc_attr($conversation->message_id); ?>">
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo esc_html($conversation->conversation_id); ?></td>
|
||||
<td><?php echo esc_html($conversation->campaign_id); ?></td>
|
||||
<td><?php echo esc_html($conversation->created_at); ?></td>
|
||||
<td><?php echo esc_html($conversation->message_id); ?></td>
|
||||
<td><?php echo esc_html($conversation->scheduled_for_timestamp); ?></td>
|
||||
<td><?php echo esc_html($conversation->from_email); ?></td>
|
||||
<td><?php echo esc_html($conversation->to_email); ?></td>
|
||||
<td><?php echo esc_html($conversation->subject); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<button type="button" id="delete-selected-messages" class="button button-primary">
|
||||
<?php esc_html_e('Delete Items', 'rl-mailwarmer'); ?>
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Select all messages
|
||||
const checkAll = document.getElementById('check-all-messages');
|
||||
const checkboxes = document.querySelectorAll('input[name="selected_messages[]"]');
|
||||
|
||||
checkAll.addEventListener('change', function () {
|
||||
checkboxes.forEach(checkbox => checkbox.checked = checkAll.checked);
|
||||
});
|
||||
|
||||
// Handle delete button click
|
||||
document.getElementById('delete-selected-messages').addEventListener('click', function () {
|
||||
const selectedIds = Array.from(checkboxes)
|
||||
.filter(checkbox => checkbox.checked)
|
||||
.map(checkbox => checkbox.value);
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert('<?php esc_html_e('Please select at least one message to delete.', 'rl-mailwarmer'); ?>');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('<?php esc_html_e('Are you sure you want to delete the selected messages?', 'rl-mailwarmer'); ?>')) {
|
||||
jQuery.post(ajaxurl, {
|
||||
action: 'rl_delete_messages',
|
||||
message_ids: selectedIds,
|
||||
}, function (response) {
|
||||
if (response.success) {
|
||||
alert('<?php esc_html_e('Selected messages deleted successfully.', 'rl-mailwarmer'); ?>');
|
||||
location.reload(); // Reload the page to update the list
|
||||
} else {
|
||||
alert('<?php esc_html_e('Failed to delete messages.', 'rl-mailwarmer'); ?>');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
add_action('wp_ajax_rl_delete_messages', function () {
|
||||
global $wpdb;
|
||||
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_send_json_error(['message' => __('Permission denied.', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
$message_ids = isset($_POST['message_ids']) ? array_map('intval', $_POST['message_ids']) : [];
|
||||
|
||||
if (empty($message_ids)) {
|
||||
wp_send_json_error(['message' => __('No messages selected.', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
$table_name = $wpdb->prefix . 'rl_mailwarmer_messages';
|
||||
|
||||
// Delete selected messages
|
||||
$placeholders = implode(',', array_fill(0, count($message_ids), '%d'));
|
||||
$query = "DELETE FROM $table_name WHERE id IN ($placeholders)";
|
||||
$result = $wpdb->query($wpdb->prepare($query, $message_ids));
|
||||
|
||||
if ($result === false) {
|
||||
wp_send_json_error(['message' => __('Failed to delete messages.', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Messages deleted successfully.', 'rl-mailwarmer')]);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* 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",
|
||||
'scheduled',
|
||||
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_message_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.success + ' with ' + response.data.failure + ' failures' ;
|
||||
} 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_message_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()");
|
||||
$results = RL_MailWarmer_Message_Handler::process_pending_messages();
|
||||
|
||||
wp_send_json_success($results);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(['message' => $e->getMessage()]);
|
||||
}
|
||||
});
|
||||
143
includes/class-rl-mailwarmer-post-tables.php
Normal file
143
includes/class-rl-mailwarmer-post-tables.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
class PostTypeList {
|
||||
private string $post_type;
|
||||
private array $labels;
|
||||
private int $items_per_page;
|
||||
private int $paged;
|
||||
private array $additional_query_args;
|
||||
|
||||
public function __construct(string $post_type, array $labels = [], array $additional_query_args = []) {
|
||||
$this->post_type = $post_type;
|
||||
$this->items_per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 25;
|
||||
$this->paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
|
||||
|
||||
// Base query args
|
||||
$base_args = [
|
||||
'orderby' => 'title',
|
||||
'order' => 'ASC',
|
||||
'post_type' => $this->post_type,
|
||||
'posts_per_page' => $this->items_per_page,
|
||||
'paged' => $this->paged,
|
||||
];
|
||||
|
||||
// Add owner_id filter only for non-administrators
|
||||
if (!current_user_can('administrator')) {
|
||||
$base_args['meta_query'] = array(
|
||||
array(
|
||||
'key' => 'owner_id',
|
||||
'value' => get_current_user_id(),
|
||||
'compare' => '='
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Merge with any additional query args
|
||||
$this->additional_query_args = array_merge($base_args, $additional_query_args);
|
||||
|
||||
// Default labels that can be overridden
|
||||
$this->labels = array_merge([
|
||||
'title' => ucfirst($post_type) . 's',
|
||||
'no_items' => 'No ' . strtolower($post_type) . 's found.',
|
||||
'delete_confirm' => 'Are you sure you want to delete this ' . strtolower($post_type) . '?'
|
||||
], $labels);
|
||||
}
|
||||
|
||||
public function render(): void {
|
||||
$query = new WP_Query($this->additional_query_args);
|
||||
$edit_url_base = "/dashboard/" . $this->post_type . "s/edit-" . $this->post_type . "?edit=";
|
||||
?>
|
||||
<div class="wrap">
|
||||
<!-- <h1><?php //echo esc_html($this->labels['title']); ?></h1> -->
|
||||
|
||||
|
||||
|
||||
<!-- Items list -->
|
||||
<div class="post-type-list">
|
||||
<?php if ($query->have_posts()) : ?>
|
||||
<?php
|
||||
while ($query->have_posts()) : $query->the_post();
|
||||
$post_id = get_the_ID();
|
||||
?>
|
||||
<div class="post-type-item">
|
||||
<div class="post-type-content">
|
||||
<a href="<?php echo get_permalink(); ?>" class="post-type-name">
|
||||
<?php the_title(); ?>
|
||||
</a>
|
||||
<div class="row-actions">
|
||||
<span class="edit">
|
||||
<a href="<?php echo $edit_url_base . $post_id; ?>">Edit</a> |
|
||||
</span>
|
||||
<span class="trash">
|
||||
<?php echo $this->get_delete_link($post_id); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endwhile; ?>
|
||||
<?php else : ?>
|
||||
<p><?php echo $this->labels['no_items']; ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="page-nav">
|
||||
<div class="tablenav-pages">
|
||||
<?php
|
||||
echo paginate_links(array(
|
||||
'base' => add_query_arg('paged', '%#%'),
|
||||
'format' => '',
|
||||
'prev_text' => __('«'),
|
||||
'next_text' => __('»'),
|
||||
'total' => $query->max_num_pages,
|
||||
'current' => $this->paged
|
||||
));
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php wp_reset_postdata(); ?>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function handlePerPageChange(select) {
|
||||
// Remove the paged parameter if it exists in the URL
|
||||
var form = document.getElementById('items-per-page-form');
|
||||
var currentPath = window.location.pathname;
|
||||
|
||||
// Remove any existing page/X/ from the path
|
||||
currentPath = currentPath.replace(/page\/\d+\/?/, '');
|
||||
|
||||
// Update form action to use the cleaned path
|
||||
form.action = currentPath;
|
||||
// console.log(currentPath);
|
||||
|
||||
// Submit the form
|
||||
form.submit();
|
||||
}
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
protected function get_delete_link($post_id) {
|
||||
if (!can_delete_post($post_id)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$current_url = add_query_arg(array()); // Gets current URL with existing parameters
|
||||
$delete_url = wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'action' => 'delete',
|
||||
'post' => $post_id,
|
||||
'redirect_to' => urlencode($current_url)
|
||||
),
|
||||
$current_url // Use current page URL instead of admin URL
|
||||
),
|
||||
'delete-post_' . $post_id
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s" onclick="return confirm(\'Are you sure you want to delete this item?\');" class="delete">Delete</a>',
|
||||
esc_url($delete_url)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ class RL_MailWarmer_Scheduler {
|
|||
add_filter('cron_schedules', [__CLASS__, 'add_cron_timings']);
|
||||
|
||||
// Call local functions to call the remote classes, so we can debug cron jobs
|
||||
add_action('rl_mailwarmer_process_messages', [__CLASS__, 'process_pending_messages']);
|
||||
add_action('rl_mailwarmer_process_upcoming_conversations', [__CLASS__, 'process_upcoming_conversations']);
|
||||
add_action('rl_mailwarmer_process_messages', [__CLASS__, 'cron_process_pending_messages']);
|
||||
add_action('rl_mailwarmer_process_upcoming_conversations', [__CLASS__, 'cron_process_upcoming_conversations']);
|
||||
// add_action('rl_mailwarmer_process_messages', [RL_MailWarmer_Message_Handler::class, 'process_pending_messages']);
|
||||
// add_action('rl_mailwarmer_process_upcoming_conversations', [RL_MailWarmer_Conversation_Handler::class, 'process_upcoming_conversations']);
|
||||
|
||||
|
|
@ -79,16 +79,16 @@ class RL_MailWarmer_Scheduler {
|
|||
/**
|
||||
* Process pending messages by delegating to the Message Handler.
|
||||
*/
|
||||
public static function process_pending_messages() {
|
||||
// log_to_file("schedule_cron_jobs ====================== Running Cron to process messages ========================");
|
||||
// RL_MailWarmer_Message_Handler::process_pending_messages();
|
||||
public static function cron_process_pending_messages() {
|
||||
// action_log("====================== Running Cron to process messages ========================");
|
||||
RL_MailWarmer_Message_Handler::process_pending_messages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process pending conversations by delegating to the Message Handler.
|
||||
*/
|
||||
public static function process_upcoming_conversations() {
|
||||
// log_to_file("schedule_cron_jobs ====================== Running Cron to process conversations ========================");
|
||||
// RL_MailWarmer_Message_Handler::process_upcoming_conversations();
|
||||
public static function cron_process_upcoming_conversations() {
|
||||
// action_log("====================== Running Cron to process conversations ========================");
|
||||
RL_MailWarmer_Conversation_Handler::process_upcoming_conversations();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,138 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* AJAX endpoints for domain, campaign, and email account management
|
||||
*/
|
||||
|
||||
// DOMAIN MANAGEMENT AJAX ENDPOINTS
|
||||
|
||||
/**
|
||||
* Create or update a domain via AJAX
|
||||
*/
|
||||
add_action('wp_ajax_rl_mailwarmer_save_domain', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'domain_form_nonce')) {
|
||||
wp_send_json_error(__('Security check failed', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
$current_user_id = get_current_user_id();
|
||||
$post_title = strtolower(sanitize_text_field($_POST['domain_name']));
|
||||
$cloudflare_email = isset($_POST['cloudflare_api_email']) ? sanitize_email($_POST['cloudflare_api_email']) : '';
|
||||
$cloudflare_key = isset($_POST['cloudflare_api_key']) ? sanitize_text_field($_POST['cloudflare_api_key']) : '';
|
||||
$domain_in_use = isset($_POST['domain_in_use']) ? $_POST['domain_in_use'] : '';
|
||||
$update_dns = isset($_POST['update_dns']) ? $_POST['update_dns'] : '';
|
||||
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
||||
$new_post = ($post_id == 0);
|
||||
|
||||
// Check for existing domain with same name for new posts
|
||||
if ($new_post) {
|
||||
$args = [
|
||||
'post_type' => 'domain',
|
||||
'post_status' => 'any',
|
||||
'title' => $post_title,
|
||||
'fields' => 'ids',
|
||||
'posts_per_page' => 1
|
||||
];
|
||||
|
||||
$query = new WP_Query($args);
|
||||
if ($query->have_posts()) {
|
||||
wp_send_json_error(['message' => __('Domain already exists.', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare post data
|
||||
$post_data = array(
|
||||
'post_title' => $post_title,
|
||||
'post_type' => 'domain',
|
||||
'post_status' => 'publish'
|
||||
);
|
||||
|
||||
if ($post_id) {
|
||||
$post_data['ID'] = $post_id;
|
||||
$post_id = wp_update_post($post_data);
|
||||
} else {
|
||||
$post_id = wp_insert_post($post_data);
|
||||
}
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
wp_send_json_error(['message' => $post_id->get_error_message()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update meta fields
|
||||
update_post_meta($post_id, 'cloudflare_api_email', $cloudflare_email);
|
||||
update_post_meta($post_id, 'cloudflare_api_key', $cloudflare_key);
|
||||
update_post_meta($post_id, 'domain_in_use', $domain_in_use);
|
||||
|
||||
if ($new_post) {
|
||||
// Set owner to current user for new domains
|
||||
update_post_meta($post_id, 'owner_id', $current_user_id);
|
||||
|
||||
// Run domain health check for new domains
|
||||
RL_MailWarmer_Domain_Helper::save_domain_health_report($post_id);
|
||||
}
|
||||
|
||||
// Handle DNS updates if requested
|
||||
if ($update_dns) {
|
||||
$results = RL_MailWarmer_Domain_Helper::fix_deliverability_dns_issues($post_id);
|
||||
}
|
||||
|
||||
// Handle MX records based on domain usage
|
||||
if (!$domain_in_use) {
|
||||
$server = get_field('defaut_mailferno_mx', 'option');
|
||||
$update_MX_result = RL_MailWarmer_Domain_Helper::update_mx_record($post_id, $server->post_title, 0, $ttl = 3600);
|
||||
}
|
||||
|
||||
// Return success response with post ID and permalink
|
||||
wp_send_json_success([
|
||||
'message' => $new_post ? __('Domain added successfully.', 'rl-mailwarmer') : __('Domain updated successfully.', 'rl-mailwarmer'),
|
||||
'post_id' => $post_id,
|
||||
'permalink' => get_permalink($post_id)
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete a domain via AJAX
|
||||
*/
|
||||
add_action('wp_ajax_rl_mailwarmer_delete_domain', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'domain_delete_nonce')) {
|
||||
wp_send_json_error(__('Security check failed', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
||||
|
||||
if (!$post_id) {
|
||||
wp_send_json_error(__('Invalid domain ID', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify post type and user permissions
|
||||
$post = get_post($post_id);
|
||||
if (!$post || $post->post_type !== 'domain') {
|
||||
wp_send_json_error(__('Invalid domain', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
$owner_id = get_post_meta($post_id, 'owner_id', true);
|
||||
if ($owner_id != get_current_user_id() && !current_user_can('manage_options')) {
|
||||
wp_send_json_error(__('Permission denied', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the domain
|
||||
$result = wp_delete_post($post_id, true);
|
||||
|
||||
if (!$result) {
|
||||
wp_send_json_error(__('Failed to delete domain', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Domain deleted successfully', 'rl-mailwarmer')]);
|
||||
});
|
||||
|
||||
add_action('wp_ajax_rl_mailwarmer_check_domain_health', function () {
|
||||
// log_to_file("wp_ajax_rl_mailwarmer_check_domain_health called");
|
||||
// Verify nonce
|
||||
|
|
@ -223,6 +356,304 @@ add_action('wp_ajax_rl_mailwarmer_create_dns_backup', function () {
|
|||
}
|
||||
});
|
||||
|
||||
// CAMPAIGN MANAGEMENT AJAX ENDPOINTS
|
||||
|
||||
/**
|
||||
* Create or update a campaign via AJAX
|
||||
*/
|
||||
add_action('wp_ajax_rl_mailwarmer_save_campaign', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'campaign_form_nonce')) {
|
||||
wp_send_json_error(__('Security check failed', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
$current_user_id = get_current_user_id();
|
||||
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
||||
$new_post = ($post_id == 0);
|
||||
$calculate_timeline = isset($_POST['calculate_timeline']) ? true : false;
|
||||
$target_profession = false;
|
||||
$target_profession_other = false;
|
||||
|
||||
// Prepare post data
|
||||
$post_data = array(
|
||||
'post_title' => sanitize_text_field($_POST['campaign_name']),
|
||||
'post_type' => 'campaign',
|
||||
'post_status' => 'publish'
|
||||
);
|
||||
|
||||
if ($post_id) {
|
||||
$post_data['ID'] = $post_id;
|
||||
$post_id = wp_update_post($post_data);
|
||||
} else {
|
||||
$post_id = wp_insert_post($post_data);
|
||||
// Generate tracking ID for new campaigns
|
||||
RL_MailWarmer_Campaign_Helper::generate_campaign_tracking_id($post_id);
|
||||
}
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
wp_send_json_error(['message' => $post_id->get_error_message()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update campaign enabled status
|
||||
$campaign_enabled = isset($_POST['enabled']) ? true : false;
|
||||
update_post_meta($post_id, 'enabled', $campaign_enabled);
|
||||
|
||||
// Update ACF fields
|
||||
$acf_fields = [
|
||||
'domain_id',
|
||||
'email_accounts',
|
||||
'start_date',
|
||||
'warmup_period',
|
||||
'starting_volume',
|
||||
'target_volume',
|
||||
'weekend_reduction_factor',
|
||||
'target_profession',
|
||||
'target_profession_other',
|
||||
'campaign_conversation_topics'
|
||||
];
|
||||
|
||||
foreach ($acf_fields as $field) {
|
||||
if (isset($_POST[$field])) {
|
||||
switch ($field) {
|
||||
case 'email_accounts':
|
||||
$campaign_email_accounts = $_POST[$field];
|
||||
$is_limited = false;
|
||||
|
||||
// Check if any email account has limited access
|
||||
foreach ($campaign_email_accounts as $account_id) {
|
||||
$limited_access = get_post_meta($account_id, 'limited_access', true);
|
||||
if ($limited_access === true || $limited_access === '1') {
|
||||
$is_limited = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update campaign_limited flag
|
||||
update_post_meta($post_id, 'campaign_limited', $is_limited);
|
||||
update_field($field, $campaign_email_accounts, $post_id);
|
||||
break;
|
||||
|
||||
case 'domain_id':
|
||||
try {
|
||||
update_field('domain', $_POST['domain_id'], $post_id);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(['message' => $e->getMessage()]);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'target_profession':
|
||||
$target_profession = $_POST[$field];
|
||||
break;
|
||||
|
||||
case 'target_profession_other':
|
||||
$target_profession_other = $_POST[$field];
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
update_field($field, sanitize_text_field($_POST[$field]), $post_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($target_profession_other) {
|
||||
$target_profession = $target_profession_other;
|
||||
}
|
||||
|
||||
// If no target profession is set, choose one from the default profession pool.
|
||||
if ( ! $target_profession ) {
|
||||
$professions = rl_get_textarea_meta_as_array('option', 'default_profession_pool');
|
||||
if ( ! empty($professions) ) {
|
||||
// Assign a random profession from the list
|
||||
$target_profession = $professions[array_rand($professions)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set campaign owner
|
||||
update_post_meta($post_id, 'owner_id', $current_user_id);
|
||||
|
||||
// Calculate timeline if needed
|
||||
if ($new_post || $calculate_timeline) {
|
||||
RL_MailWarmer_DB_Helper::delete_all_conversations_messages($post_id);
|
||||
RL_MailWarmer_Campaign_Helper::calculate_campaign_timeline($post_id);
|
||||
}
|
||||
|
||||
// Return success response
|
||||
wp_send_json_success([
|
||||
'message' => $new_post ? __('Campaign added successfully.', 'rl-mailwarmer') : __('Campaign updated successfully.', 'rl-mailwarmer'),
|
||||
'post_id' => $post_id,
|
||||
'permalink' => get_permalink($post_id)
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete a campaign via AJAX
|
||||
*/
|
||||
add_action('wp_ajax_rl_mailwarmer_delete_campaign', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'campaign_delete_nonce')) {
|
||||
wp_send_json_error(__('Security check failed', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
||||
|
||||
if (!$post_id) {
|
||||
wp_send_json_error(__('Invalid campaign ID', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify post type
|
||||
$post = get_post($post_id);
|
||||
if (!$post || $post->post_type !== 'campaign') {
|
||||
wp_send_json_error(__('Invalid campaign', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
$owner_id = get_post_meta($post_id, 'owner_id', true);
|
||||
if ($owner_id != get_current_user_id() && !current_user_can('manage_options')) {
|
||||
wp_send_json_error(__('Permission denied', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete campaign messages and timeline first
|
||||
RL_MailWarmer_DB_Helper::delete_all_conversations_messages($post_id);
|
||||
|
||||
// Delete the campaign
|
||||
$result = wp_delete_post($post_id, true);
|
||||
|
||||
if (!$result) {
|
||||
wp_send_json_error(__('Failed to delete campaign', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Campaign deleted successfully', 'rl-mailwarmer')]);
|
||||
});
|
||||
|
||||
// EMAIL ACCOUNT MANAGEMENT AJAX ENDPOINTS
|
||||
|
||||
/**
|
||||
* Create or update an email account via AJAX
|
||||
*/
|
||||
add_action('wp_ajax_rl_mailwarmer_save_email_account', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'email_form_nonce')) {
|
||||
wp_send_json_error(__('Security check failed', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
$current_user_id = get_current_user_id();
|
||||
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
||||
$post_title = strtolower(sanitize_email($_POST['email_address']));
|
||||
$new_post = ($post_id == 0);
|
||||
|
||||
// Check for existing email account with same address for new posts
|
||||
if ($new_post) {
|
||||
$args = [
|
||||
'post_type' => 'email-account',
|
||||
'post_status' => 'any',
|
||||
'title' => $post_title,
|
||||
'fields' => 'ids',
|
||||
'posts_per_page' => 1
|
||||
];
|
||||
|
||||
$query = new WP_Query($args);
|
||||
if ($query->have_posts()) {
|
||||
wp_send_json_error(['message' => __('Email account already exists.', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare post data
|
||||
$post_data = array(
|
||||
'post_title' => $post_title,
|
||||
'post_type' => 'email-account',
|
||||
'post_status' => 'publish'
|
||||
);
|
||||
|
||||
if ($post_id) {
|
||||
$post_data['ID'] = $post_id;
|
||||
$post_id = wp_update_post($post_data);
|
||||
} else {
|
||||
$post_id = wp_insert_post($post_data);
|
||||
}
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
wp_send_json_error(['message' => $post_id->get_error_message()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update meta fields
|
||||
$meta_fields = [
|
||||
'domain_id', 'mail_password', 'full_name', 'email_signature',
|
||||
'email_provider', 'smtp_password', 'smtp_server', 'smtp_port',
|
||||
'imap_password', 'imap_server', 'imap_port'
|
||||
];
|
||||
|
||||
foreach ($meta_fields as $field) {
|
||||
if (isset($_POST[$field])) {
|
||||
update_post_meta($post_id, $field, sanitize_text_field($_POST[$field]));
|
||||
}
|
||||
}
|
||||
|
||||
// For new accounts, set ownership and include in warmup pool
|
||||
if ($new_post) {
|
||||
update_post_meta($post_id, 'owner_id', $current_user_id);
|
||||
update_post_meta($post_id, 'include_in_warmup_pool', $current_user_id);
|
||||
}
|
||||
|
||||
// Return success response
|
||||
wp_send_json_success([
|
||||
'message' => $new_post ? __('Email account added successfully.', 'rl-mailwarmer') : __('Email account updated successfully.', 'rl-mailwarmer'),
|
||||
'post_id' => $post_id,
|
||||
'permalink' => get_permalink($post_id)
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete an email account via AJAX
|
||||
*/
|
||||
add_action('wp_ajax_rl_mailwarmer_delete_email_account', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'email_delete_nonce')) {
|
||||
wp_send_json_error(__('Security check failed', 'rl-mailwarmer'));
|
||||
}
|
||||
|
||||
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
|
||||
|
||||
if (!$post_id) {
|
||||
wp_send_json_error(__('Invalid email account ID', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify post type
|
||||
$post = get_post($post_id);
|
||||
if (!$post || $post->post_type !== 'email-account') {
|
||||
wp_send_json_error(__('Invalid email account', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify ownership
|
||||
$owner_id = get_post_meta($post_id, 'owner_id', true);
|
||||
if ($owner_id != get_current_user_id() && !current_user_can('manage_options')) {
|
||||
wp_send_json_error(__('Permission denied', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the email account
|
||||
$result = wp_delete_post($post_id, true);
|
||||
|
||||
if (!$result) {
|
||||
wp_send_json_error(__('Failed to delete email account', 'rl-mailwarmer'));
|
||||
return;
|
||||
}
|
||||
|
||||
wp_send_json_success(['message' => __('Email account deleted successfully', 'rl-mailwarmer')]);
|
||||
});
|
||||
|
||||
add_action('wp_ajax_rl_mailwarmer_modify_email_account', function () {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'modify_email_account_nonce')) {
|
||||
|
|
@ -271,3 +702,226 @@ add_action('wp_ajax_rl_mailwarmer_check_mail_login', function () {
|
|||
wp_send_json_error($e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Dashboard Modal AJAX handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate domain for adding to Mailferno
|
||||
*/
|
||||
add_action('wp_ajax_validate_domain', function() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'dashboard_modal_nonce')) {
|
||||
wp_send_json_error(['message' => __('Security check failed', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
// Get and validate domain
|
||||
$domain = isset($_POST['domain']) ? sanitize_text_field($_POST['domain']) : '';
|
||||
|
||||
if (empty($domain)) {
|
||||
wp_send_json_error(['message' => __('Domain name is required', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
// Check if domain already exists
|
||||
$existing_domain = get_page_by_title($domain, OBJECT, 'domain');
|
||||
if ($existing_domain) {
|
||||
wp_send_json_error(['message' => __('Domain already exists in your account', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if domain is valid (DNS resolves)
|
||||
if (!checkdnsrr($domain, 'A') && !checkdnsrr($domain, 'AAAA')) {
|
||||
wp_send_json_error(['message' => __('Domain does not resolve to a valid IP address', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if domain uses CloudFlare
|
||||
$uses_cloudflare = false;
|
||||
|
||||
// Get the NS records for the domain
|
||||
$ns_records = dns_get_record($domain, DNS_NS);
|
||||
|
||||
// If no NS records found directly, try with www
|
||||
if (empty($ns_records)) {
|
||||
$ns_records = dns_get_record('www.' . $domain, DNS_NS);
|
||||
}
|
||||
|
||||
// Check if any NS record contains cloudflare
|
||||
if (!empty($ns_records)) {
|
||||
foreach ($ns_records as $record) {
|
||||
if (isset($record['target']) && stripos($record['target'], 'cloudflare') !== false) {
|
||||
$uses_cloudflare = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'isValid' => true,
|
||||
'usesCloudFlare' => $uses_cloudflare
|
||||
]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Verify CloudFlare credentials and check MX records
|
||||
*/
|
||||
add_action('wp_ajax_verify_cloudflare', function() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'dashboard_modal_nonce')) {
|
||||
wp_send_json_error(['message' => __('Security check failed', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
$domain = isset($_POST['domain']) ? sanitize_text_field($_POST['domain']) : '';
|
||||
$email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
|
||||
$key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : '';
|
||||
|
||||
if (empty($domain) || empty($email) || empty($key)) {
|
||||
wp_send_json_error(['message' => __('Missing required fields', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use existing cloudflare helper to verify credentials and get zone ID
|
||||
try {
|
||||
$client = new \GuzzleHttp\Client([
|
||||
'base_uri' => 'https://api.cloudflare.com/client/v4/',
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
]);
|
||||
|
||||
$response = $client->get('zones', [
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$key}",
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'query' => ['name' => $domain],
|
||||
]);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($data['result'][0]['id'])) {
|
||||
wp_send_json_error(['message' => __('Domain not found in your CloudFlare account', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
|
||||
$zone_id = $data['result'][0]['id'];
|
||||
|
||||
// Check if MX records exist
|
||||
$has_mx = false;
|
||||
|
||||
// Query CloudFlare for MX records
|
||||
$mx_response = $client->get("zones/{$zone_id}/dns_records", [
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$key}",
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'query' => ['type' => 'MX'],
|
||||
]);
|
||||
|
||||
$mx_data = json_decode($mx_response->getBody()->getContents(), true);
|
||||
|
||||
if (isset($mx_data['result']) && !empty($mx_data['result'])) {
|
||||
$has_mx = true;
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'zoneId' => $zone_id,
|
||||
'hasMX' => $has_mx
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
wp_send_json_error(['message' => __('Failed to verify CloudFlare credentials: ', 'rl-mailwarmer') . $e->getMessage()]);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create domain with all configurations
|
||||
*/
|
||||
add_action('wp_ajax_create_domain', function() {
|
||||
// Verify nonce
|
||||
if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'dashboard_modal_nonce')) {
|
||||
wp_send_json_error(['message' => __('Security check failed', 'rl-mailwarmer')]);
|
||||
}
|
||||
|
||||
// Get domain data
|
||||
$domain_data_json = isset($_POST['domainData']) ? $_POST['domainData'] : '';
|
||||
|
||||
if (empty($domain_data_json)) {
|
||||
wp_send_json_error(['message' => __('No domain data provided', 'rl-mailwarmer')]);
|
||||
return;
|
||||
}
|
||||
|
||||
$domain_data = json_decode(stripslashes($domain_data_json), true);
|
||||
|
||||
// Create the domain post
|
||||
$current_user_id = get_current_user_id();
|
||||
$domain_name = $domain_data['domain'];
|
||||
|
||||
$post_data = array(
|
||||
'post_title' => $domain_name,
|
||||
'post_type' => 'domain',
|
||||
'post_status' => 'publish'
|
||||
);
|
||||
|
||||
$post_id = wp_insert_post($post_data);
|
||||
|
||||
if (is_wp_error($post_id)) {
|
||||
wp_send_json_error(['message' => $post_id->get_error_message()]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save domain meta data
|
||||
update_post_meta($post_id, 'cloudflare_api_email', $domain_data['cloudflareEmail']);
|
||||
update_post_meta($post_id, 'cloudflare_api_key', $domain_data['cloudflareKey']);
|
||||
update_post_meta($post_id, 'cloudflare_zone_id', $domain_data['cloudflareZoneId']);
|
||||
update_post_meta($post_id, 'domain_in_use', !$domain_data['configureMailferno']);
|
||||
update_post_meta($post_id, 'owner_id', $current_user_id);
|
||||
|
||||
// Run a domain health check
|
||||
$domain_report_id = RL_MailWarmer_Domain_Helper::save_domain_health_report($post_id);
|
||||
|
||||
// Handle DNS configuration if requested
|
||||
if ($domain_data['configureMailferno']) {
|
||||
$server = get_field('defaut_mailferno_mx', 'option');
|
||||
RL_MailWarmer_Domain_Helper::update_mx_record($post_id, $server->post_title, 0, 3600);
|
||||
}
|
||||
|
||||
// Fix DNS records
|
||||
$results = RL_MailWarmer_Domain_Helper::fix_deliverability_dns_issues($post_id);
|
||||
|
||||
// Create email accounts if provided
|
||||
$email_accounts = [];
|
||||
|
||||
if (!empty($domain_data['emailAccounts'])) {
|
||||
foreach ($domain_data['emailAccounts'] as $email) {
|
||||
// Extract username from email
|
||||
$username = explode('@', $email)[0];
|
||||
|
||||
// Create email account
|
||||
$email_post_data = array(
|
||||
'post_title' => $email,
|
||||
'post_type' => 'email-account',
|
||||
'post_status' => 'publish'
|
||||
);
|
||||
|
||||
$email_post_id = wp_insert_post($email_post_data);
|
||||
|
||||
if (!is_wp_error($email_post_id)) {
|
||||
// Set default password and metadata
|
||||
update_post_meta($email_post_id, 'domain_id', $post_id);
|
||||
update_post_meta($email_post_id, 'mail_password', wp_generate_password(12, false));
|
||||
update_post_meta($email_post_id, 'full_name', ucwords(str_replace(['.', '-', '_'], ' ', $username)));
|
||||
update_post_meta($email_post_id, 'owner_id', $current_user_id);
|
||||
update_post_meta($email_post_id, 'include_in_warmup_pool', true);
|
||||
|
||||
$email_accounts[] = $email_post_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'message' => __('Domain added successfully. ' . (!empty($email_accounts) ? count($email_accounts) . ' email accounts created.' : ''), 'rl-mailwarmer'),
|
||||
'domain_id' => $post_id,
|
||||
'email_accounts' => $email_accounts
|
||||
]);
|
||||
});
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
* Set the CUSTOM_DEBUG_LOG file in wp-config.php
|
||||
*
|
||||
*/
|
||||
function log_to_file($message, $data = false){
|
||||
function log_to_file($message, $data = null, $enable_backtrace = false) {
|
||||
if ($message) {
|
||||
$log_File = CUSTOM_DEBUG_LOG;
|
||||
|
||||
|
|
@ -21,13 +21,48 @@ function log_to_file($message, $data = false){
|
|||
|
||||
// Convert arrays and objects to JSON format
|
||||
if (is_array($data) || is_object($data)) {
|
||||
$data = json_encode($data);
|
||||
$data = json_encode($data, JSON_PRETTY_PRINT);
|
||||
$message = $message . "\r\n" . $data;
|
||||
} else if ($data) {
|
||||
$message = $message . " " . $data;
|
||||
}
|
||||
|
||||
error_log("[$date] " . $message ."\r\n",3,$log_File);
|
||||
// Include backtrace information if enabled
|
||||
if ($enable_backtrace) {
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$callingFunction = $backtrace[1]['function']; // Access the calling function name
|
||||
$backtrace_info = json_encode($backtrace, JSON_PRETTY_PRINT);
|
||||
$message .= "\r\nBacktrace:\r\n" . $backtrace_info;
|
||||
}
|
||||
|
||||
error_log("[$date] " . $message . "\r\n", 3, $log_File);
|
||||
}
|
||||
}
|
||||
|
||||
function action_log($message, $data = null, $enable_backtrace = false) {
|
||||
if ($message) {
|
||||
$log_File = ACTION_LOG;
|
||||
|
||||
$date = new DateTime('now', new DateTimeZone('America/Chicago'));
|
||||
$date = $date->format("Y/m/d h:i:s");
|
||||
|
||||
// Convert arrays and objects to JSON format
|
||||
if (is_array($data) || is_object($data)) {
|
||||
$data = json_encode($data, JSON_PRETTY_PRINT);
|
||||
$message = $message . "\r\n" . $data;
|
||||
} else if ($data) {
|
||||
$message = $message . " " . $data;
|
||||
}
|
||||
|
||||
// Include backtrace information if enabled
|
||||
if ($enable_backtrace) {
|
||||
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
$callingFunction = $backtrace[1]['function']; // Access the calling function name
|
||||
$backtrace_info = json_encode($backtrace, JSON_PRETTY_PRINT);
|
||||
$message .= "\r\nBacktrace:\r\n" . $backtrace_info;
|
||||
}
|
||||
|
||||
error_log("[$date] " . $message . "\r\n", 3, $log_File);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
185
includes/rl-mailwarmer-importer.php
Normal file
185
includes/rl-mailwarmer-importer.php
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
add_action('admin_menu', function () {
|
||||
add_menu_page(
|
||||
__('Import Data', 'rl-mailwarmer'), // Page title
|
||||
__('Import Data', 'rl-mailwarmer'), // Menu title
|
||||
'manage_options', // Capability
|
||||
'rl-mailwarmer-import-data', // Menu slug
|
||||
'rl_mailwarmer_render_import_page', // Callback function
|
||||
'dashicons-upload', // Icon
|
||||
25 // Position
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Render the Import Data admin page.
|
||||
*/
|
||||
function rl_mailwarmer_render_import_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a nonce and handle file upload
|
||||
if (isset($_POST['rl_import_nonce']) && wp_verify_nonce($_POST['rl_import_nonce'], 'rl_import_data')) {
|
||||
if (!empty($_FILES['domain_csv']['tmp_name'])) {
|
||||
rl_mailwarmer_process_domain_csv($_FILES['domain_csv']['tmp_name']);
|
||||
}
|
||||
|
||||
if (!empty($_FILES['email_account_csv']['tmp_name'])) {
|
||||
rl_mailwarmer_process_email_account_csv($_FILES['email_account_csv']['tmp_name']);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php esc_html_e('Import Data', 'rl-mailwarmer'); ?></h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<?php wp_nonce_field('rl_import_data', 'rl_import_nonce'); ?>
|
||||
|
||||
<h2><?php esc_html_e('Import Domains', 'rl-mailwarmer'); ?></h2>
|
||||
<p><?php esc_html_e('Upload a CSV file to import domains.', 'rl-mailwarmer'); ?></p>
|
||||
<input type="file" name="domain_csv" accept=".csv" />
|
||||
|
||||
<h2><?php esc_html_e('Import Email Accounts', 'rl-mailwarmer'); ?></h2>
|
||||
<p><?php esc_html_e('Upload a CSV file to import email accounts.', 'rl-mailwarmer'); ?></p>
|
||||
<input type="file" name="email_account_csv" accept=".csv" />
|
||||
|
||||
<p><button type="submit" class="button button-primary"><?php esc_html_e('Upload Files', 'rl-mailwarmer'); ?></button></p>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
function rl_mailwarmer_process_domain_csv($file_path) {
|
||||
$handle = fopen($file_path, 'r');
|
||||
if (!$handle) {
|
||||
echo '<div class="error"><p>' . __('Failed to open the domain CSV file.', 'rl-mailwarmer') . '</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the headers
|
||||
$headers = fgetcsv($handle);
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$data = array_combine($headers, $row);
|
||||
|
||||
if (empty($data['name'])) {
|
||||
continue; // Skip invalid rows
|
||||
}
|
||||
|
||||
// Create or update the domain post
|
||||
$existing_domain = get_posts([
|
||||
'post_type' => 'domain',
|
||||
'title' => $data['name'],
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
$post_id = $existing_domain[0] ?? null;
|
||||
|
||||
$post_data = [
|
||||
'post_title' => $data['name'],
|
||||
'post_type' => 'domain',
|
||||
'post_status' => 'publish',
|
||||
];
|
||||
|
||||
if ($post_id) {
|
||||
$post_data['ID'] = $post_id;
|
||||
}
|
||||
|
||||
$post_id = wp_insert_post($post_data);
|
||||
|
||||
if (!is_wp_error($post_id)) {
|
||||
update_post_meta($post_id, 'cloudflare_api_email', sanitize_text_field($data['cloudflare_api_email']));
|
||||
update_post_meta($post_id, 'cloudflare_api_key', sanitize_text_field($data['cloudflare_api_key']));
|
||||
log_to_file("rl_mailwarmer_process_domain_csv - Imported domain: " . $data['name']);
|
||||
} else {
|
||||
|
||||
log_to_file("rl_mailwarmer_process_domain_csv - ERROR importing domain: " . $data['name']);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
echo '<div class="updated"><p>' . __('Domains imported successfully.', 'rl-mailwarmer') . '</p></div>';
|
||||
}
|
||||
|
||||
function rl_mailwarmer_process_email_account_csv($file_path) {
|
||||
$handle = fopen($file_path, 'r');
|
||||
if (!$handle) {
|
||||
echo '<div class="error"><p>' . __('Failed to open the email account CSV file.', 'rl-mailwarmer') . '</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the headers
|
||||
$headers = fgetcsv($handle);
|
||||
|
||||
while (($row = fgetcsv($handle)) !== false) {
|
||||
$data = array_combine($headers, $row);
|
||||
|
||||
if (empty($data['Email Address']) || empty($data['Password'])) {
|
||||
continue; // Skip invalid rows
|
||||
}
|
||||
|
||||
if (!empty($data['Name'])) {
|
||||
$name = sanitize_text_field($data['Name']);
|
||||
}
|
||||
|
||||
if (!empty($data['Signature'])) {
|
||||
$signature = sanitize_textarea_field(wp_slash($data['Signature']));
|
||||
// log_to_file("rl_mailwarmer_process_email_account_csv - Signature: {$signature}");
|
||||
}
|
||||
|
||||
// Match the email account to a domain
|
||||
$domain_name = strtolower(substr(strrchr($data['Email Address'], "@"), 1));
|
||||
$domain = get_posts([
|
||||
'post_type' => 'domain',
|
||||
'title' => $domain_name,
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 1,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
// Match the email provider by name
|
||||
$email_provider = $data['Email Provider'];
|
||||
|
||||
// Check if this is a scrubber account
|
||||
if (!empty($data['Is Scrubber'])) {
|
||||
$include_in_scrubber_pool = true;
|
||||
} else {
|
||||
$include_in_scrubber_pool = false;
|
||||
}
|
||||
|
||||
// Create or update the email account post
|
||||
$post_data = [
|
||||
'post_title' => strtolower($data['Email Address']),
|
||||
'post_type' => 'email-account',
|
||||
'post_status' => 'publish',
|
||||
'meta_input' => [
|
||||
'full_name' => $name,
|
||||
'mail_password' => sanitize_text_field($data['Password']),
|
||||
'email_provider' => $email_provider,
|
||||
'email_signature' => $signature,
|
||||
'include_in_scrubber_pool' => $include_in_scrubber_pool,
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($domain)) {
|
||||
$post_data['meta_input']['domain'] = $domain[0];
|
||||
}
|
||||
|
||||
$post_id = wp_insert_post($post_data);
|
||||
|
||||
if (!is_wp_error($post_id)) {
|
||||
// $email_account_connection_status = RL_MailWarmer_Email_Helper::check_mail_login($post_id);
|
||||
log_to_file("rl_mailwarmer_process_email_account_csv - Imported email {$data['Email Address']}: ");
|
||||
} else {
|
||||
log_to_file("rl_mailwarmer_process_email_account_csv - ERROR importing email: " . $data['Email Address']);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
echo '<div class="updated"><p>' . __('Email accounts imported successfully.', 'rl-mailwarmer') . '</p></div>';
|
||||
}
|
||||
38
js/admin-update-mx.js
Normal file
38
js/admin-update-mx.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
jQuery(document).ready(function ($) {
|
||||
$('#update-mx-record-button').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// const postId = $('#post_ID').val();
|
||||
const postId = rlMailWarmerMx.post_id;
|
||||
const action = $('#mx_action').val();
|
||||
const content = $('#mx_content').val();
|
||||
const priority = $('#mx_priority').val();
|
||||
const ttl = $('#mx_ttl').val();
|
||||
|
||||
$('#mx-update-result').html('<p>Updating MX record...</p>');
|
||||
|
||||
$.ajax({
|
||||
url: rlMailWarmerMx.ajax_url,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'rl_mailwarmer_update_mx_record',
|
||||
post_id: postId,
|
||||
action_type: action,
|
||||
content: content,
|
||||
priority: priority,
|
||||
ttl: ttl,
|
||||
security: rlMailWarmerMx.nonce,
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
$('#mx-update-result').html('<p>' + response.data + '</p>');
|
||||
} else {
|
||||
$('#mx-update-result').html('<p>Error: ' + response.data + '</p>');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$('#mx-update-result').html('<p>AJAX Error: ' + error + '</p>');
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
195
js/campaign-form.js
Normal file
195
js/campaign-form.js
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
* Campaign form AJAX handling
|
||||
*/
|
||||
jQuery(document).ready(function($) {
|
||||
// Handle campaign form submission
|
||||
$('#campaign-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Show loading state
|
||||
const $submitButton = $(this).find('input[type="submit"]');
|
||||
const originalText = $submitButton.val();
|
||||
$submitButton.val('Processing...').prop('disabled', true);
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(this);
|
||||
formData.append('action', 'rl_mailwarmer_save_campaign');
|
||||
formData.append('security', campaign_form_vars.nonce);
|
||||
|
||||
// Process checkbox fields
|
||||
formData.set('enabled', $('#enabled').is(':checked') ? '1' : '');
|
||||
formData.set('calculate_timeline', $('#calculate_timeline').is(':checked') ? '1' : '');
|
||||
|
||||
// AJAX request
|
||||
$.ajax({
|
||||
url: campaign_form_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Display success message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success is-dismissible')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
|
||||
// Reset form or redirect if not staying on page
|
||||
if (!$('#stay_on_page').is(':checked') && response.data.permalink) {
|
||||
// window.location.href = response.data.permalink;
|
||||
return;
|
||||
} else if (!$('#stay_on_page').is(':checked')) {
|
||||
window.location.href = campaign_form_vars.campaigns_page;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset form for new submission if staying on page
|
||||
if (!$('#post_id').val()) {
|
||||
$('#campaign-form')[0].reset();
|
||||
}
|
||||
} else {
|
||||
// Display error message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error is-dismissible')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// Display error message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error is-dismissible')
|
||||
.html('<p>An error occurred: ' + error + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
},
|
||||
complete: function() {
|
||||
// Restore button state
|
||||
$submitButton.val(originalText).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle delete campaign button click
|
||||
$('.delete-campaign-btn').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!confirm('Are you sure you want to delete this campaign? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postId = $(this).data('id');
|
||||
|
||||
// Show loading state
|
||||
$(this).text('Deleting...').prop('disabled', true);
|
||||
|
||||
// AJAX request to delete campaign
|
||||
$.ajax({
|
||||
url: campaign_form_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'rl_mailwarmer_delete_campaign',
|
||||
security: campaign_form_vars.delete_nonce,
|
||||
post_id: postId
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Redirect to campaigns list page
|
||||
window.location.href = campaign_form_vars.campaigns_page;
|
||||
} else {
|
||||
alert(response.data.message || 'Error deleting campaign');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('An error occurred while deleting the campaign');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Filter email accounts to only display those matching the selected domain
|
||||
*/
|
||||
const $domainSelect = $('#domain_id');
|
||||
const $emailSelect = $('#email_accounts');
|
||||
const $emailOptions = $emailSelect.find('option');
|
||||
|
||||
function filterEmailAccounts() {
|
||||
const selectedDomain = $domainSelect.val();
|
||||
|
||||
$emailOptions.hide();
|
||||
if (selectedDomain) {
|
||||
$emailOptions.each(function() {
|
||||
const $option = $(this);
|
||||
const email = $option.text();
|
||||
const domain = $domainSelect.find('option:selected').text();
|
||||
|
||||
if (email.endsWith('@' + domain)) {
|
||||
$option.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$domainSelect.on('change', function() {
|
||||
$emailSelect.val([]); // Clear selection
|
||||
filterEmailAccounts();
|
||||
});
|
||||
|
||||
// Initial filter
|
||||
filterEmailAccounts();
|
||||
|
||||
/**
|
||||
* Show the Other Profession field when the Target Profession is set to "Other"
|
||||
*/
|
||||
const $professionSelect = $('#target_profession');
|
||||
const $otherField = $('#target_profession_other').closest('tr');
|
||||
|
||||
function toggleOtherField() {
|
||||
if ($professionSelect.val() === 'Other') {
|
||||
$otherField.show();
|
||||
} else {
|
||||
$otherField.hide();
|
||||
$('#target_profession_other').val('');
|
||||
}
|
||||
}
|
||||
|
||||
$professionSelect.on('change', toggleOtherField);
|
||||
|
||||
// Initial state
|
||||
toggleOtherField();
|
||||
|
||||
// Helper function to add dismiss button to notices
|
||||
function addNoticeDismissButton() {
|
||||
$('.notice.is-dismissible').each(function() {
|
||||
const $notice = $(this);
|
||||
const $button = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
|
||||
|
||||
// Only add the button if it doesn't exist already
|
||||
if ($notice.find('.notice-dismiss').length === 0) {
|
||||
$notice.append($button);
|
||||
|
||||
// Handle the dismiss button click
|
||||
$button.on('click', function() {
|
||||
$notice.slideUp('fast', function() {
|
||||
$notice.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize dismiss buttons
|
||||
addNoticeDismissButton();
|
||||
});
|
||||
484
js/dashboard-modal.js
Normal file
484
js/dashboard-modal.js
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
/**
|
||||
* Dashboard Modal & Multi-step Form Handlers
|
||||
*/
|
||||
jQuery(document).ready(function($) {
|
||||
// Modal elements
|
||||
const $fabButton = $('.mf-floating-action-button');
|
||||
const $modalOverlay = $('.mf-modal-overlay');
|
||||
const $modalContainer = $('.mf-modal-container');
|
||||
const $modalClose = $('.mf-modal-close');
|
||||
const $backBtn = $('.mf-back-btn');
|
||||
const $nextBtn = $('.mf-next-btn');
|
||||
|
||||
// Step indicators
|
||||
const $stepDots = $('.mf-step-dot');
|
||||
let $steps = $('.mf-step');
|
||||
|
||||
// Selection buttons
|
||||
const $selectionButtons = $('.mf-selection-button');
|
||||
|
||||
// Current step and selected type
|
||||
let currentStep = 0;
|
||||
let selectedType = '';
|
||||
let domainData = {
|
||||
domain: '',
|
||||
isValid: false,
|
||||
usesCloudFlare: false,
|
||||
cloudflareZoneId: '',
|
||||
cloudflareEmail: '',
|
||||
cloudflareKey: '',
|
||||
hasMX: false,
|
||||
configureMailferno: false,
|
||||
emailAccounts: []
|
||||
};
|
||||
|
||||
// Open Modal
|
||||
$fabButton.on('click', function() {
|
||||
resetForm();
|
||||
$modalOverlay.addClass('active');
|
||||
});
|
||||
|
||||
// Close Modal
|
||||
$modalClose.on('click', closeModal);
|
||||
$modalOverlay.on('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
function closeModal() {
|
||||
$modalOverlay.removeClass('active');
|
||||
resetForm();
|
||||
}
|
||||
|
||||
// Reset Form
|
||||
function resetForm() {
|
||||
currentStep = 0;
|
||||
selectedType = '';
|
||||
domainData = {
|
||||
domain: '',
|
||||
isValid: false,
|
||||
usesCloudFlare: false,
|
||||
cloudflareZoneId: '',
|
||||
cloudflareEmail: '',
|
||||
cloudflareKey: '',
|
||||
hasMX: false,
|
||||
configureMailferno: false,
|
||||
emailAccounts: []
|
||||
};
|
||||
|
||||
$selectionButtons.removeClass('selected');
|
||||
updateStepIndicators();
|
||||
showCurrentStep();
|
||||
$('.mf-validation-error').hide();
|
||||
$('.mf-success-message, .mf-error-message').remove();
|
||||
$('.mf-loading').hide();
|
||||
$('#mf-domain-input').val('');
|
||||
$('#mf-cloudflare-email').val('');
|
||||
$('#mf-cloudflare-key').val('');
|
||||
$('.mf-mx-option').removeClass('selected');
|
||||
$('.mf-email-row:not(:first)').remove();
|
||||
$('.mf-email-row input').val('');
|
||||
}
|
||||
|
||||
// Selection buttons
|
||||
$selectionButtons.on('click', function() {
|
||||
$selectionButtons.removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
selectedType = $(this).data('type');
|
||||
$nextBtn.removeClass('disabled');
|
||||
});
|
||||
|
||||
// Navigation buttons
|
||||
$backBtn.on('click', function() {
|
||||
if (currentStep > 0) {
|
||||
currentStep--;
|
||||
updateStepIndicators();
|
||||
showCurrentStep();
|
||||
}
|
||||
});
|
||||
|
||||
$nextBtn.on('click', function() {
|
||||
if ($(this).hasClass('disabled')) return;
|
||||
|
||||
// Handle first step (selection)
|
||||
if (currentStep === 0) {
|
||||
if (!selectedType) {
|
||||
alert('Please select an option to continue');
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up next steps based on selection
|
||||
setupStepsByType(selectedType);
|
||||
currentStep++;
|
||||
updateStepIndicators();
|
||||
showCurrentStep();
|
||||
return;
|
||||
}
|
||||
|
||||
// Domain specific step handling
|
||||
if (selectedType === 'domain') {
|
||||
const result = handleDomainStep(currentStep);
|
||||
if (!result) return; // If step validation fails, don't proceed
|
||||
}
|
||||
|
||||
// If we reach here, move to next step
|
||||
currentStep++;
|
||||
updateStepIndicators();
|
||||
showCurrentStep();
|
||||
});
|
||||
|
||||
// Update step indicators
|
||||
function updateStepIndicators() {
|
||||
$stepDots.removeClass('active completed');
|
||||
|
||||
// Update for current setup
|
||||
$stepDots.each(function(index) {
|
||||
if (index < currentStep) {
|
||||
$(this).addClass('completed');
|
||||
} else if (index === currentStep) {
|
||||
$(this).addClass('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Update back button visibility
|
||||
if (currentStep === 0) {
|
||||
$backBtn.hide();
|
||||
} else {
|
||||
$backBtn.show();
|
||||
}
|
||||
|
||||
// Update next button text
|
||||
const isLastStep = (currentStep === $steps.length - 1);
|
||||
$nextBtn.text(isLastStep ? 'Next' : 'Next');
|
||||
|
||||
// When on selection step, disable next until selection made
|
||||
if (currentStep === 0) {
|
||||
$nextBtn.toggleClass('disabled', !selectedType);
|
||||
} else {
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
// Show current step
|
||||
function showCurrentStep() {
|
||||
$steps.removeClass('active');
|
||||
$steps.eq(currentStep).addClass('active');
|
||||
}
|
||||
|
||||
// Setup steps by selected type
|
||||
function setupStepsByType(type) {
|
||||
// Hide all steps except the first one
|
||||
$steps.not(':first').remove();
|
||||
|
||||
// Clear step indicators
|
||||
$('.mf-steps-indicator').empty();
|
||||
|
||||
let stepCount = 1; // Selection step
|
||||
|
||||
if (type === 'domain') {
|
||||
// Step 1: Input domain
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Enter Domain Name</h3>')
|
||||
.append('<div class="mf-form-field"><label for="mf-domain-input">Domain Name</label><input type="text" id="mf-domain-input" placeholder="example.com"><div class="mf-validation-error" id="domain-validation-error">Please enter a valid domain name</div></div>')
|
||||
.append('<div class="mf-loading" style="display:none;"><div class="mf-loading-spinner"></div><p>Validating domain...</p></div>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
|
||||
// Step 2: CloudFlare verification
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Verify CloudFlare Connection</h3>')
|
||||
.append('<div class="mf-form-field"><label for="mf-cloudflare-email">CloudFlare Email</label><input type="email" id="mf-cloudflare-email" placeholder="your@email.com"></div>')
|
||||
.append('<div class="mf-form-field"><label for="mf-cloudflare-key">CloudFlare API Key</label><input type="password" id="mf-cloudflare-key" placeholder="Your API key"></div>')
|
||||
.append('<div class="mf-validation-error" id="cloudflare-validation-error">Please enter valid CloudFlare credentials</div>')
|
||||
.append('<div class="mf-loading" style="display:none;"><div class="mf-loading-spinner"></div><p>Verifying CloudFlare credentials...</p></div>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
|
||||
// Step 3: MX Configuration
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Email Server Configuration</h3>')
|
||||
.append('<p>How would you like to configure email for this domain?</p>')
|
||||
.append('<div class="mf-mx-options"><div class="mf-mx-option" data-option="mailferno"><h4>Use Mailferno MX Server</h4><p>Configure this domain to use Mailferno\'s email server. Best if this is a new domain not currently used for email.</p></div><div class="mf-mx-option" data-option="existing"><h4>Keep Existing Configuration</h4><p>Keep your current email server configuration. Best if this domain is already being used for email.</p></div></div>')
|
||||
.append('<div class="mf-validation-error" id="mx-validation-error">Please select an MX configuration option</div>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
|
||||
// Step 4: Email accounts
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Add Email Accounts</h3>')
|
||||
.append('<p>Would you like to create email accounts for this domain now?</p>')
|
||||
.append('<div class="mf-email-list"><div class="mf-email-row"><input type="text" class="mf-email-local" placeholder="username"><span>@'+domainData.domain+'</span><button type="button" class="mf-remove-email-btn">×</button></div></div>')
|
||||
.append('<button type="button" class="mf-add-email-btn">+ Add Another Email</button>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
|
||||
// Final step
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Domain Setup Complete</h3>')
|
||||
.append('<div class="mf-success-message">Your domain has been successfully added to Mailferno!</div>')
|
||||
.append('<p>You can now continue to manage your domain, email accounts, and campaigns from the dashboard.</p>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
} else if (type === 'campaign') {
|
||||
// Add campaign specific steps
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Create Campaign</h3>')
|
||||
.append('<p>Campaign creation functionality will be implemented here.</p>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
|
||||
// Final step
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Campaign Created</h3>')
|
||||
.append('<div class="mf-success-message">Your campaign has been successfully created!</div>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
} else if (type === 'email') {
|
||||
// Add email account specific steps
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Create Email Account</h3>')
|
||||
.append('<p>Email account creation functionality will be implemented here.</p>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
|
||||
// Final step
|
||||
$('<div class="mf-step">')
|
||||
.append('<h3>Email Account Created</h3>')
|
||||
.append('<div class="mf-success-message">Your email account has been successfully created!</div>')
|
||||
.appendTo($modalContainer.find('.mf-modal-body'));
|
||||
stepCount++;
|
||||
}
|
||||
|
||||
// Create step indicators
|
||||
for (let i = 0; i < stepCount; i++) {
|
||||
$('<div class="mf-step-dot">')
|
||||
.addClass(i === 0 ? 'completed' : (i === 1 ? 'active' : ''))
|
||||
.appendTo($('.mf-steps-indicator'));
|
||||
}
|
||||
|
||||
// Make steps accessible
|
||||
$steps = $('.mf-step');
|
||||
|
||||
// Setup event handlers for the domain steps
|
||||
if (type === 'domain') {
|
||||
setupDomainStepHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
// Domain step specific handlers
|
||||
function setupDomainStepHandlers() {
|
||||
// Step 1: Domain validation
|
||||
$('#mf-domain-input').on('input', function() {
|
||||
// Basic validation
|
||||
const domain = $(this).val().trim();
|
||||
const isValid = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(domain);
|
||||
|
||||
if (isValid) {
|
||||
$('#domain-validation-error').hide();
|
||||
domainData.domain = domain;
|
||||
} else {
|
||||
$('#domain-validation-error').show();
|
||||
}
|
||||
});
|
||||
|
||||
// Step 3: MX Configuration
|
||||
$('.mf-mx-option').on('click', function() {
|
||||
$('.mf-mx-option').removeClass('selected');
|
||||
$(this).addClass('selected');
|
||||
domainData.configureMailferno = ($(this).data('option') === 'mailferno');
|
||||
});
|
||||
|
||||
// Step 4: Email accounts
|
||||
$('.mf-add-email-btn').on('click', function() {
|
||||
const $newRow = $('.mf-email-row').first().clone();
|
||||
$newRow.find('input').val('');
|
||||
$newRow.insertBefore($(this));
|
||||
});
|
||||
|
||||
// Remove email button
|
||||
$(document).on('click', '.mf-remove-email-btn', function() {
|
||||
if ($('.mf-email-row').length > 1) {
|
||||
$(this).closest('.mf-email-row').remove();
|
||||
} else {
|
||||
$('.mf-email-row').find('input').val('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Domain step validation and AJAX handling
|
||||
function handleDomainStep(step) {
|
||||
// Adjust step to match domain-specific numbering (after selection)
|
||||
const domainStep = step - 1;
|
||||
|
||||
switch(domainStep) {
|
||||
case 0: // Domain input validation
|
||||
const domain = $('#mf-domain-input').val().trim();
|
||||
if (!domain || !/^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(domain)) {
|
||||
$('#domain-validation-error').show();
|
||||
return false;
|
||||
}
|
||||
|
||||
domainData.domain = domain;
|
||||
|
||||
// Show loading indicator
|
||||
$steps.eq(currentStep).find('.mf-loading').show();
|
||||
$nextBtn.addClass('disabled');
|
||||
|
||||
// AJAX validation call to check if domain is valid and uses CloudFlare
|
||||
$.ajax({
|
||||
url: dashboard_modal_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'validate_domain',
|
||||
security: dashboard_modal_vars.nonce,
|
||||
domain: domain
|
||||
},
|
||||
success: function(response) {
|
||||
$steps.eq(currentStep).find('.mf-loading').hide();
|
||||
|
||||
if (response.success) {
|
||||
domainData.isValid = true;
|
||||
domainData.usesCloudFlare = response.data.usesCloudFlare;
|
||||
|
||||
if (!domainData.usesCloudFlare) {
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">This domain is not using CloudFlare nameservers. Please configure CloudFlare for this domain before continuing.</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update domain text in email step
|
||||
$('.mf-email-row span').text('@' + domain);
|
||||
|
||||
// Continue to next step
|
||||
$nextBtn.removeClass('disabled');
|
||||
$nextBtn.trigger('click');
|
||||
} else {
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">' + response.data.message + '</div>');
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$steps.eq(currentStep).find('.mf-loading').hide();
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">Error validating domain. Please try again.</div>');
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
});
|
||||
return false; // Prevent automatic next step - we'll handle it in AJAX callback
|
||||
|
||||
case 1: // CloudFlare verification
|
||||
const email = $('#mf-cloudflare-email').val().trim();
|
||||
const key = $('#mf-cloudflare-key').val().trim();
|
||||
|
||||
if (!email || !key) {
|
||||
$('#cloudflare-validation-error').show();
|
||||
return false;
|
||||
}
|
||||
|
||||
domainData.cloudflareEmail = email;
|
||||
domainData.cloudflareKey = key;
|
||||
|
||||
// Show loading indicator
|
||||
$steps.eq(currentStep).find('.mf-loading').show();
|
||||
$nextBtn.addClass('disabled');
|
||||
|
||||
// AJAX call to verify CloudFlare credentials
|
||||
$.ajax({
|
||||
url: dashboard_modal_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'verify_cloudflare',
|
||||
security: dashboard_modal_vars.nonce,
|
||||
domain: domainData.domain,
|
||||
email: email,
|
||||
key: key
|
||||
},
|
||||
success: function(response) {
|
||||
$steps.eq(currentStep).find('.mf-loading').hide();
|
||||
|
||||
if (response.success) {
|
||||
domainData.cloudflareZoneId = response.data.zoneId;
|
||||
domainData.hasMX = response.data.hasMX;
|
||||
|
||||
// Continue to next step
|
||||
$nextBtn.removeClass('disabled');
|
||||
$nextBtn.trigger('click');
|
||||
} else {
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">' + response.data.message + '</div>');
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$steps.eq(currentStep).find('.mf-loading').hide();
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">Error verifying CloudFlare credentials. Please try again.</div>');
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
});
|
||||
return false; // Prevent automatic next step - we'll handle it in AJAX callback
|
||||
|
||||
case 2: // MX Configuration
|
||||
if (!$('.mf-mx-option.selected').length) {
|
||||
$('#mx-validation-error').show();
|
||||
return false;
|
||||
}
|
||||
|
||||
domainData.configureMailferno = ($('.mf-mx-option.selected').data('option') === 'mailferno');
|
||||
return true;
|
||||
|
||||
case 3: // Email accounts
|
||||
domainData.emailAccounts = [];
|
||||
let allValid = true;
|
||||
|
||||
$('.mf-email-row').each(function() {
|
||||
const username = $(this).find('.mf-email-local').val().trim();
|
||||
if (username) {
|
||||
domainData.emailAccounts.push(username + '@' + domainData.domain);
|
||||
}
|
||||
});
|
||||
|
||||
if (allValid) {
|
||||
// Create domain via AJAX
|
||||
$nextBtn.addClass('disabled');
|
||||
|
||||
$.ajax({
|
||||
url: dashboard_modal_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'create_domain',
|
||||
security: dashboard_modal_vars.nonce,
|
||||
domainData: JSON.stringify(domainData)
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Update final step
|
||||
$steps.eq(currentStep + 1).find('.mf-success-message').text(response.data.message);
|
||||
|
||||
// Continue to next step
|
||||
$nextBtn.removeClass('disabled');
|
||||
$nextBtn.trigger('click');
|
||||
|
||||
// Reload page after a short delay to show updated dashboard
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
} else {
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">' + response.data.message + '</div>');
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$steps.eq(currentStep).append('<div class="mf-error-message">Error creating domain. Please try again.</div>');
|
||||
$nextBtn.removeClass('disabled');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return allValid;
|
||||
|
||||
case 4: // Final step - just close modal
|
||||
closeModal();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
150
js/domain-form.js
Normal file
150
js/domain-form.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* Domain form AJAX handling
|
||||
*/
|
||||
jQuery(document).ready(function($) {
|
||||
// console.log("Domain form loaded");
|
||||
// Handle domain form submission
|
||||
$('#domain-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Show loading state
|
||||
const $submitButton = $(this).find('input[type="submit"]');
|
||||
const originalText = $submitButton.val();
|
||||
$submitButton.val('Processing...').prop('disabled', true);
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(this);
|
||||
formData.append('action', 'rl_mailwarmer_save_domain');
|
||||
formData.append('security', domain_form_vars.nonce);
|
||||
|
||||
// Process checkbox fields
|
||||
formData.set('domain_in_use', $('#domain_in_use').is(':checked') ? '1' : '');
|
||||
formData.set('update_dns', $('#update_dns').is(':checked') ? '1' : '');
|
||||
|
||||
// AJAX request
|
||||
$.ajax({
|
||||
url: domain_form_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
console.log("Domain added");
|
||||
// Display success message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success is-dismissible')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
|
||||
// Reset form or redirect if not staying on page
|
||||
if (!$('#stay_on_page').is(':checked') && response.data.permalink) {
|
||||
// window.location.href = response.data.permalink;
|
||||
return;
|
||||
} else if (!$('#stay_on_page').is(':checked')) {
|
||||
window.location.href = domain_form_vars.domains_page;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset form for new submission if staying on page
|
||||
if (!$('#post_id').val()) {
|
||||
$('#domain-form')[0].reset();
|
||||
}
|
||||
} else {
|
||||
// Display error message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error is-dismissible')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// Display error message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error is-dismissible')
|
||||
.html('<p>An error occurred: ' + error + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
},
|
||||
complete: function() {
|
||||
// Restore button state
|
||||
$submitButton.val(originalText).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle delete domain button click
|
||||
$('.delete-domain-btn').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!confirm('Are you sure you want to delete this domain? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postId = $(this).data('id');
|
||||
|
||||
// Show loading state
|
||||
$(this).text('Deleting...').prop('disabled', true);
|
||||
|
||||
// AJAX request to delete domain
|
||||
$.ajax({
|
||||
url: domain_form_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'rl_mailwarmer_delete_domain',
|
||||
security: domain_form_vars.delete_nonce,
|
||||
post_id: postId
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Redirect to domains list page
|
||||
window.location.href = domain_form_vars.domains_page;
|
||||
} else {
|
||||
alert(response.data.message || 'Error deleting domain');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('An error occurred while deleting the domain');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle advanced settings
|
||||
$('.advanced-toggle').on('click', function() {
|
||||
$(this).parent().find('.advanced-content').slideToggle();
|
||||
});
|
||||
|
||||
// Helper function to add dismiss button to notices
|
||||
function addNoticeDismissButton() {
|
||||
$('.notice.is-dismissible').each(function() {
|
||||
const $notice = $(this);
|
||||
const $button = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
|
||||
|
||||
// Only add the button if it doesn't exist already
|
||||
if ($notice.find('.notice-dismiss').length === 0) {
|
||||
$notice.append($button);
|
||||
|
||||
// Handle the dismiss button click
|
||||
$button.on('click', function() {
|
||||
$notice.slideUp('fast', function() {
|
||||
$notice.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize dismiss buttons
|
||||
addNoticeDismissButton();
|
||||
});
|
||||
169
js/email-account-form.js
Normal file
169
js/email-account-form.js
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* Email account form AJAX handling
|
||||
*/
|
||||
jQuery(document).ready(function($) {
|
||||
// Handle email account form submission
|
||||
$('#email-account-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Email validation
|
||||
if (!validateEmail()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
const $submitButton = $(this).find('input[type="submit"]');
|
||||
const originalText = $submitButton.val();
|
||||
$submitButton.val('Processing...').prop('disabled', true);
|
||||
|
||||
// Get form data
|
||||
const formData = new FormData(this);
|
||||
formData.append('action', 'rl_mailwarmer_save_email_account');
|
||||
formData.append('security', email_form_vars.nonce);
|
||||
|
||||
// AJAX request
|
||||
$.ajax({
|
||||
url: email_form_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Display success message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-error')
|
||||
.addClass('notice notice-success is-dismissible')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
|
||||
// Reset form or redirect if not staying on page
|
||||
if (!$('#stay_on_page').is(':checked') && response.data.permalink) {
|
||||
// window.location.href = response.data.permalink;
|
||||
return;
|
||||
} else if (!$('#stay_on_page').is(':checked')) {
|
||||
window.location.href = email_form_vars.email_accounts_page;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset form for new submission if staying on page
|
||||
if (!$('#post_id').val()) {
|
||||
$('#email-account-form')[0].reset();
|
||||
}
|
||||
} else {
|
||||
// Display error message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error is-dismissible')
|
||||
.html('<p>' + response.data.message + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// Display error message
|
||||
$('#form-messages')
|
||||
.removeClass('notice-success')
|
||||
.addClass('notice notice-error is-dismissible')
|
||||
.html('<p>An error occurred: ' + error + '</p>')
|
||||
.show();
|
||||
|
||||
// Add a dismiss button to the notice
|
||||
addNoticeDismissButton();
|
||||
},
|
||||
complete: function() {
|
||||
// Restore button state
|
||||
$submitButton.val(originalText).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle delete email account button click
|
||||
$('.delete-email-account-btn').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!confirm('Are you sure you want to delete this email account? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postId = $(this).data('id');
|
||||
|
||||
// Show loading state
|
||||
$(this).text('Deleting...').prop('disabled', true);
|
||||
|
||||
// AJAX request to delete email account
|
||||
$.ajax({
|
||||
url: email_form_vars.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'rl_mailwarmer_delete_email_account',
|
||||
security: email_form_vars.delete_nonce,
|
||||
post_id: postId
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
// Redirect to email accounts list page
|
||||
window.location.href = email_form_vars.email_accounts_page;
|
||||
} else {
|
||||
alert(response.data.message || 'Error deleting email account');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert('An error occurred while deleting the email account');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Email validation function
|
||||
function validateEmail() {
|
||||
const domain_id = $("#domain_id").val();
|
||||
const email = $("#email_address").val();
|
||||
|
||||
if (!domain_id || !email) return true;
|
||||
|
||||
const domain_text = $("#domain_id option:selected").text();
|
||||
const emailDomain = email.split("@")[1];
|
||||
|
||||
if (emailDomain !== domain_text) {
|
||||
alert("Email address must match the selected domain: " + domain_text);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add validation to form submission
|
||||
$("#email_address, #domain_id").on("change", validateEmail);
|
||||
|
||||
// Toggle advanced settings
|
||||
$('.advanced-toggle').on('click', function() {
|
||||
$(this).parent().find('.advanced-content').slideToggle();
|
||||
});
|
||||
|
||||
// Helper function to add dismiss button to notices
|
||||
function addNoticeDismissButton() {
|
||||
$('.notice.is-dismissible').each(function() {
|
||||
const $notice = $(this);
|
||||
const $button = $('<button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>');
|
||||
|
||||
// Only add the button if it doesn't exist already
|
||||
if ($notice.find('.notice-dismiss').length === 0) {
|
||||
$notice.append($button);
|
||||
|
||||
// Handle the dismiss button click
|
||||
$button.on('click', function() {
|
||||
$notice.slideUp('fast', function() {
|
||||
$notice.remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize dismiss buttons
|
||||
addNoticeDismissButton();
|
||||
});
|
||||
36
js/generate-conversation.js
Normal file
36
js/generate-conversation.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
jQuery(document).ready(function ($) {
|
||||
$('#generate-conversation').on('click', function () {
|
||||
const initiatedBy = $('#initiated_by').val();
|
||||
const subject = $('#subject').val();
|
||||
const length = $('#length').val();
|
||||
|
||||
$.ajax({
|
||||
url: rlGenerateConversation.ajax_url,
|
||||
type: 'POST',
|
||||
data: {
|
||||
action: 'rl_generate_conversation',
|
||||
nonce: rlGenerateConversation.nonce,
|
||||
post_id: rlGenerateConversation.post_id,
|
||||
initiated_by: initiatedBy,
|
||||
subject: subject,
|
||||
length: length,
|
||||
},
|
||||
beforeSend: function () {
|
||||
$('#generate-conversation').text('Generating...').prop('disabled', true);
|
||||
},
|
||||
success: function (response) {
|
||||
$('#generate-conversation').text('Generate Conversation').prop('disabled', false);
|
||||
|
||||
if (response.success) {
|
||||
console.log('Conversation generated successfully! ID: ' + response.data.conversation_id);
|
||||
} else {
|
||||
alert('Error: ' + response.data.message);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#generate-conversation').text('Generate Conversation').prop('disabled', false);
|
||||
alert('Failed to generate conversation. Please try again.');
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -17,13 +17,14 @@ jQuery(document).ready(function ($) {
|
|||
success: function (response) {
|
||||
console.log(response);
|
||||
if (response.success) {
|
||||
const timeline = response.data;
|
||||
let output = '<p>Timeline Generated:</p><ul>';
|
||||
$.each(timeline, function (date, volume) {
|
||||
output += `<li><strong>${date}:</strong> ${volume} emails</li>`;
|
||||
});
|
||||
output += '</ul>';
|
||||
$('#generate-timeline-result').html(output);
|
||||
console.log(response.data);
|
||||
// const timeline = response.data;
|
||||
// let output = '<p>Timeline Generated:</p><ul>';
|
||||
// $.each(timeline, function (date, volume) {
|
||||
// output += `<li><strong>${date}:</strong> ${volume} emails</li>`;
|
||||
// });
|
||||
// output += '</ul>';
|
||||
$('#generate-timeline-result').html(response.data);
|
||||
} else {
|
||||
$('#generate-timeline-result').html('<p>Error: ' + response.data + '</p>');
|
||||
}
|
||||
|
|
|
|||
35
js/public-check-domain-health.js
Normal file
35
js/public-check-domain-health.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
jQuery(document).ready(function ($) {
|
||||
// console.log("Loaded check-domain-health.js");
|
||||
$('#check-domain-health-button').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
//var postId = $('#post_ID').val(); // Get the current post ID
|
||||
var postData = {
|
||||
action: 'rl_mailwarmer_check_domain_health',
|
||||
post_id: rlMailWarmer_public.post_id,
|
||||
security: rlMailWarmer_public.nonce,
|
||||
};
|
||||
// console.log("AJAX URL: " + rlMailWarmer_public.ajax_url);
|
||||
// console.log("Post Action: " + postData.action);
|
||||
// console.log("Post postId: " + postData.post_id);
|
||||
// console.log("Post security: " + postData.security);
|
||||
|
||||
$('#domain-health-result').html('<p>Checking domain health...</p>');
|
||||
|
||||
$.ajax({
|
||||
url: rlMailWarmer_public.ajax_url,
|
||||
type: 'POST',
|
||||
data: postData,
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
$('#domain-health-result').html('<p>Report saved successfully. Post ID: ' + response.data + '</p>');
|
||||
} else {
|
||||
$('#domain-health-result').html('<p>Error: ' + response.data + '</p>');
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$('#domain-health-result').html('<p>AJAX Error: ' + error + '</p>');
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
35
js/rl-generate-accounts.js
Normal file
35
js/rl-generate-accounts.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
jQuery(document).ready(function ($) {
|
||||
$('#rl_generate_accounts_button').on('click', function () {
|
||||
const postId = $('#post_ID').val();
|
||||
const quantity = $('#rl_generate_account_qty').val();
|
||||
|
||||
$('#rl_generate_accounts_result').html('<p>Generating accounts...</p>');
|
||||
|
||||
$.ajax({
|
||||
url: rlGenerateAccounts.ajax_url,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'rl_generate_random_accounts',
|
||||
post_id: postId,
|
||||
qty: quantity,
|
||||
security: rlGenerateAccounts.nonce,
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
const results = response.data;
|
||||
let resultHtml = '<p>Generated Accounts:</p><ul>';
|
||||
results.forEach(account => {
|
||||
resultHtml += `<li>${account.email} (Password: ${account.password})</li>`;
|
||||
});
|
||||
resultHtml += '</ul>';
|
||||
$('#rl_generate_accounts_result').html(resultHtml);
|
||||
} else {
|
||||
$('#rl_generate_accounts_result').html(`<p>Error: ${response.data}</p>`);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#rl_generate_accounts_result').html('<p>Failed to generate accounts.</p>');
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
27
js/rl-process-conversations.js
Normal file
27
js/rl-process-conversations.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
jQuery(document).ready(function ($) {
|
||||
$('#rl_process_conversations_button').on('click', function () {
|
||||
const postId = $('#post_ID').val();
|
||||
|
||||
$('#rl_process_conversations_result').html('<p>Processing conversations...</p>');
|
||||
|
||||
$.ajax({
|
||||
url: rlProcessConversations.ajax_url,
|
||||
method: 'POST',
|
||||
data: {
|
||||
action: 'rl_process_upcoming_conversations',
|
||||
post_id: postId,
|
||||
security: rlProcessConversations.nonce,
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
$('#rl_process_conversations_result').html(`<p>${response.data}</p>`);
|
||||
} else {
|
||||
$('#rl_process_conversations_result').html(`<p>Error: ${response.data}</p>`);
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
$('#rl_process_conversations_result').html('<p>Failed to process conversations.</p>');
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
14
templates/domain-list.php
Normal file
14
templates/domain-list.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
// log_to_file("Running domain template");
|
||||
|
||||
// get_header();
|
||||
|
||||
// $list = new PostTypeList('domain', [
|
||||
// 'title' => 'My Domains',
|
||||
// 'no_items' => 'You haven\'t added any domains yet.',
|
||||
// 'delete_confirm' => 'Are you sure you want to delete this domain? This action cannot be undone.'
|
||||
// ]);
|
||||
// $list->render();
|
||||
|
||||
// get_footer();
|
||||
Loading…
Add table
Reference in a new issue