From dc42392180b2d0338fe9ef699ac1737f4140768e Mon Sep 17 00:00:00 2001 From: ruben Date: Tue, 25 Feb 2025 23:57:08 -0600 Subject: [PATCH] Implement Mailferno theme enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added image optimization with WebP conversion - Updated Dashboard UI with card layout - Fixed duplicate domain/email detection - Increased default items per page to 25 - Added campaign weekend reduction factor field - Added ember animation effect in footer 🤖 Generated with Claude Code Co-Authored-By: Claude --- CLAUDE.md | 32 +++++++ footer.php | 64 +++++++++++++ functions.php | 147 +++++++++++++++++++++++++----- images/mailferno_background.webp | Bin 0 -> 6684 bytes page-campaigns.php | 2 +- page-dashboard.php | 78 +++++++++++----- page-domains.php | 2 +- page-edit-campaign.php | 152 ++++++++++++++++++++++++++----- page-edit-domain.php | 106 +++++++++++++-------- page-edit-email-account.php | 63 ++++++++----- page-email-accounts.php | 2 +- single-campaign.php | 13 ++- style.css | 5 + 13 files changed, 530 insertions(+), 136 deletions(-) create mode 100644 CLAUDE.md create mode 100644 images/mailferno_background.webp diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3fe6307 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,32 @@ +# Mailferno WordPress Theme - Development Guidelines + +## Development Environment +- This is a WordPress theme that appears to be a child theme of GeneratePress +- Project includes custom templates for a SaaS email service (Mailferno) +- WordPress core functions and AJAX are used extensively + +## Code Style Guidelines +- Indentation: 4 spaces (not tabs) +- Function/variable naming: snake_case (e.g., my_function_name) +- Constants/globals: UPPERCASE_WITH_UNDERSCORES +- Opening braces on separate lines for functions and control structures +- Add spaces after control keywords (if, for, while) + +## WordPress Patterns +- Use WordPress coding standards for hook naming +- Prefix custom functions with theme prefix to avoid conflicts +- Use WordPress sanitization functions for user input +- Validate data with WordPress validation functions +- Follow WordPress nonce verification practices for forms + +## Error Handling +- Use try/catch blocks for operations that may fail +- Check return values with is_wp_error() for WP operations +- Return early from functions when conditions aren't met +- Use log_to_file() for debug logging + +## Common Tasks +- Testing: Manual testing in WordPress admin and frontend +- Development: Use WP_DEBUG=true in wp-config.php for development +- Code organization: Group related functions together +- JavaScript: Include at bottom of template files or enqueue properly \ No newline at end of file diff --git a/footer.php b/footer.php index 684321d..9ba1c96 100644 --- a/footer.php +++ b/footer.php @@ -60,6 +60,70 @@ do_action( 'generate_after_footer' ); wp_footer(); ?> + 'after' => '', ) ); - if ( ($onboard_steps[0] || $onboard_steps[1] || $onboard_steps[2] || $onboard_steps[3]) ) : ?> -
-

Get Started:

-
    -
  1. Save your CloudFlare API Credentials
  2. -
  3. Add a Domain and verify DNS records
  4. -
  5. Add one or more Email Accounts
  6. -
  7. Create a Campaign
  8. -
+
+

Campaigns

+

Email Accounts

+

Domains

+

Messages Scheduled

+

Delivery Rate

+

Delivery Errors

+
- + + + +
diff --git a/page-edit-campaign.php b/page-edit-campaign.php index 81a5e16..0c99880 100644 --- a/page-edit-campaign.php +++ b/page-edit-campaign.php @@ -11,8 +11,10 @@ if (!defined('ABSPATH')) { // log_to_file("Campaign Edit template - Loaded"); $current_user_id = get_current_user_id(); +$calculate_timeline = true; $message = false; $new_post = true; +$campaign_enabled = true; $professions = []; @@ -30,6 +32,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['campaign_submit'])) { 'post_type' => 'campaign', 'post_status' => 'publish' ); + $calculate_timeline = isset($_POST['calculate_timeline']) ? true : false; if (isset($_POST['post_id']) && !empty($_POST['post_id'])) { @@ -38,9 +41,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['campaign_submit'])) { $post_id = wp_update_post($post_data); $new_post = false; $message = ['status' => 'success', 'message' => 'Campaign updated successfully.']; + $campaign_tracking_id = get_post_meta($post_id, 'campaign_tracking_id', true); + // log_to_file("Campaign Edit template - Campaign Tracking ID: $campaign_tracking_id"); + if ($campaign_tracking_id == '') { + RL_MailWarmer_Campaign_Helper::generate_campaign_tracking_id($post_id); + } } else { // log_to_file("Campaign Edit template - Creating new campaign"); $post_id = wp_insert_post($post_data); + RL_MailWarmer_Campaign_Helper::generate_campaign_tracking_id($post_id); $message = ['status' => 'success', 'message' => 'Campaign added successfully.']; } @@ -48,14 +57,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['campaign_submit'])) { // log_to_file("Campaign Edit template - Create/update successful!"); + $campaign_enabled = isset($_POST['enabled']) ? true : false; + // log_to_file("Campaign Edit template - 1 Campaign Enabled: {$campaign_enabled} New Post: {$new_post}"); + // var_dump($campaign_enabled); + update_post_meta($post_id, 'enabled', $campaign_enabled); + $acf_fields = [ 'domain_id', + // 'enabled', 'email_accounts', // 'num_additional_emails', 'start_date', 'warmup_period', 'starting_volume', 'target_volume', + 'weekend_reduction_factor', 'target_profession', 'target_profession_other', 'campaign_conversation_topics' @@ -66,9 +82,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['campaign_submit'])) { // log_to_file("Campaign Edit template - Checking for $field in POST: ", $_POST[$field]); switch ($field) { case 'email_accounts': - // log_to_file("Campaign Edit template - Found email_accounts in POST", $_POST[$field]); - update_field($field, $_POST[$field], $post_id); - break; + $campaign_email_accounts = $_POST[$field]; + $is_limited = false; + + // Iterate through each email account ID + foreach ($campaign_email_accounts as $account_id) { + // Retrieve the 'limited_access' meta value for the current account ID + $limited_access = get_post_meta($account_id, 'limited_access', true); + + // Check if 'limited_access' is true + if ($limited_access === true || $limited_access === '1') { + $is_limited = true; + break; // Exit the loop early as we only need one true value + } + } + + // Update 'campaign_limited' for $post_id if any account is limited + if ($is_limited) { + update_post_meta($post_id, 'campaign_limited', true); + } else { + update_post_meta($post_id, 'campaign_limited', false); + } + + // Update the field as usual + update_field($field, $campaign_email_accounts, $post_id); + break; case 'domain_id': // log_to_file("Campaign Edit template - Found domain in POST", $_POST['domain_id']); @@ -87,13 +125,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['campaign_submit'])) { } } + + // log_to_file("Campaign Edit template - Setting campaign owner_id to $current_user_id"); update_post_meta($post_id, 'owner_id', $current_user_id); - // Run a domain health check - $campaign_timeline = RL_MailWarmer_Campaign_Helper::calculate_campaign_timeline($post_id); + // Calculate a timeline + if ($new_post || $calculate_timeline) { + RL_MailWarmer_DB_Helper::delete_all_conversations_messages($post_id); + RL_MailWarmer_Campaign_Helper::calculate_campaign_timeline($post_id); + RL_MailWarmer_Campaign_Helper::fill_campaign_timeline($post_id); + // log_to_file("Campaign Edit template - Campaign timeline: ", $campaign_timeline); + } - log_to_file("Campaign Edit template - Campaign timeline: ", $campaign_timeline); } else { $message = ['status' => 'error', 'message' => 'Error: ' . $post_id->get_error_message()]; @@ -113,11 +157,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['campaign_submit'])) { if (isset($_GET['edit'])) { $post_id = intval($_GET['edit']); $new_post = false; + $campaign_enabled = get_post_meta($post_id, 'enabled') ? get_post_meta($post_id, 'enabled', true) : false; + // log_to_file("Campaign Edit template - 2 Campaign Enabled: {$campaign_enabled} New Post: {$new_post}"); + // var_dump($campaign_enabled); + // update_post_meta($post_id, 'enabled', $campaign_enabled); } else { $post_id = 0; } $campaign_data = [ 'campaign_name' => '', + // 'enabled' => '', 'domain_id' => '', 'email_accounts' => [], 'num_additional_emails' => '', @@ -125,6 +174,7 @@ $campaign_data = [ 'warmup_period' => '8', 'starting_volume' => '5', 'target_volume' => '50', + 'weekend_reduction_factor' => 25, 'target_profession' => '', 'target_profession_other' => '', 'campaign_conversation_topics' => '' @@ -139,18 +189,20 @@ if ($post_id > 0) { switch ($key) { case 'domain_id': - $domain_id = get_field('domain', $post_id); - $campaign_data[$key] = $domain_id->ID; + $domain_id = get_field('domain', $post_id) ? get_field('domain', $post_id) : ''; + if ($domain_id) { + $campaign_data[$key] = $domain_id->ID; + } break; case 'email_accounts': - $email_accounts = get_field($key, $post_id); + $email_accounts = get_field($key, $post_id) ? get_field($key, $post_id) : ''; // log_to_file("Campaign Edit template - Email accounts: ", $email_accounts); $campaign_data[$key] = $email_accounts; break; case 'start_date': - $start_date = get_field($key, $post_id); + $start_date = get_field($key, $post_id) ? get_field($key, $post_id) : ''; $campaign_data[$key] = date('Y-m-d', strtotime($start_date)); break; @@ -170,6 +222,8 @@ if ($post_id > 0) { } } +log_to_file("Campaign Edit template - Campaign data: ", $campaign_data); + // Get domains for current user $domains = get_posts([ 'orderby' => 'title', @@ -189,8 +243,14 @@ $domains = get_posts([ $professions = rl_get_textarea_meta_as_array('option', 'default_profession_pool'); // log_to_file("Campaign Edit template - Professions: ", $professions); +// log_to_file("Campaign Edit template - 3 Campaign Enabled: {$campaign_enabled} New Post: {$new_post}"); + + + + get_header(); ?> +
>
>
-

+

+ + View Campaign + +
@@ -232,6 +296,18 @@ get_header(); ?> class="regular-text" required> + + + + + class=""> + Disabled campaigns will not send any mail until they are re-enabled + + + @@ -247,8 +323,9 @@ get_header(); ?> @@ -281,8 +361,14 @@ get_header(); ?> + + + value="" required> @@ -309,6 +395,15 @@ get_header(); ?> class="small-text" min="10" max="1000" placeholder="50" required> + + + + % + Reduce traffic on weekend & holidays by this percentage + + @@ -336,6 +431,18 @@ get_header(); ?> class="regular-text" rows="5"> + + + + + class=""> + This will delete any pending messages and recreate the timeline based on the above fields + + +
@@ -344,7 +451,8 @@ get_header(); ?> value="">

- + +
diff --git a/page-edit-domain.php b/page-edit-domain.php index 088eb79..fc964a7 100644 --- a/page-edit-domain.php +++ b/page-edit-domain.php @@ -15,7 +15,7 @@ $new_post = true; // Handle form submission if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['domain_submit'])) { - $post_title = sanitize_text_field($_POST['domain_name']); + $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'] : ''; @@ -26,50 +26,76 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['domain_submit'])) { 'post_status' => 'publish' ); - // Check if editing existing post - if (isset($_POST['post_id']) && !empty($_POST['post_id'])) { - $post_data['ID'] = intval($_POST['post_id']); - $post_id = wp_update_post($post_data); - $new_post = false; - $message = ['status' => 'success', 'message' => 'Domain updated successfully.']; - } else { - $post_id = wp_insert_post($post_data); - $message = ['status' => 'success', 'message' => 'Domain added successfully.']; - } + // Check if editing existing post + if (isset($_POST['post_id']) && !empty($_POST['post_id'])) { + $post_data['ID'] = intval($_POST['post_id']); + $post_id = wp_update_post($post_data); + $new_post = false; + $message = ['status' => 'success', 'message' => 'Domain updated successfully.']; + } else { + // Prepare WP_Query to check for an existing post with the same title + $args = [ + 'post_type' => 'domain', // Replace with your post type + 'post_status' => 'any', + 'title' => $post_title, + 'fields' => 'id', + 'posts_per_page' => 1 + ]; + + $query = new WP_Query($args); - if (!is_wp_error($post_id)) { - 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 the owner_id to the creator of the post if this is a new domain - update_post_meta($post_id, 'owner_id', $current_user_id); + if ($query->have_posts()) { + log_to_file("Domain Edit template - Domain {$post_title} already exists: "); + $message = ['status' => 'error', 'message' => 'Domain already exists.']; + } else { + $post_id = wp_insert_post($post_data); + $new_post = true; + $message = ['status' => 'success', 'message' => 'Domain added successfully.']; + if (!is_wp_error($post_id)) { + 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 the owner_id to the creator of the post if this is a new domain + 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); - } - } else { - $message = ['status' => 'error', 'message' => 'Error: ' . $post_id->get_error_message()]; - // $message = 'Error: ' . $post_id->get_error_message(); - } + // Run a domain health check + $domain_report_id = RL_MailWarmer_Domain_Helper::save_domain_health_report($post_id); + } + } else { + $message = ['status' => 'error', 'message' => 'Error: ' . $post_id->get_error_message()]; + // $message = 'Error: ' . $post_id->get_error_message(); + } - if (isset($_POST['update_dns']) && !empty($_POST['update_dns'])) { - log_to_file("Domain Edit template - Running Fix Domain"); - $results = RL_MailWarmer_Domain_Helper::fix_deliverability_dns_issues($post_id); - log_to_file("Domain Edit template - Results: ", $results); - } + if (isset($_POST['update_dns']) && !empty($_POST['update_dns'])) { + log_to_file("Domain Edit template - Running Fix Domain"); + $results = RL_MailWarmer_Domain_Helper::fix_deliverability_dns_issues($post_id); + log_to_file("Domain Edit template - Results: ", $results); + } - if ($domain_in_use) { - log_to_file("Domain Edit template - Domain in use: $post_title"); - } else { - log_to_file("Domain Edit template - Domain not in use: $post_title"); - } + if ($domain_in_use) { + log_to_file("Domain Edit template - Domain in use: {$post_title}. Not modifying MX records"); + } else { + log_to_file("Domain Edit template - Domain not in use: {$post_title}. Setting MX to Mailferno server "); + // Set MX to 'server' of $post_id + $server = get_field('defaut_mailferno_mx', 'option'); + log_to_file("Domain Edit template - Default Mailferno MX Server: ", $server); + $update_MX_result = RL_MailWarmer_Domain_Helper::update_mx_record($post_id, $server->post_title, 0, $ttl = 3600); + log_to_file("Domain Edit template - CloudFlare Response: {$update_MX_result}"); + } - if ( !(isset($_POST['stay_on_page']) && ($_POST['stay_on_page'] === 'on')) ) { - // log_to_file("Domain Edit template - stay_on_page not set; redirecting!"); - wp_redirect( get_permalink( $post_id ) ); - exit; - } + if ( !(isset($_POST['stay_on_page']) && ($_POST['stay_on_page'] === 'on')) ) { + // log_to_file("Domain Edit template - stay_on_page not set; redirecting!"); + wp_redirect( get_permalink( $post_id ) ); + exit; + } + } + + // Reset post data + wp_reset_postdata(); + } + + } // Get existing post data if editing diff --git a/page-edit-email-account.php b/page-edit-email-account.php index 6373769..c7aaec7 100644 --- a/page-edit-email-account.php +++ b/page-edit-email-account.php @@ -18,8 +18,9 @@ log_to_file("Email Account Edit template - Post Data: ", $_POST); // Handle form submission if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email_submit']) && isset($_POST['email_address'])) { + $post_title = strtolower(sanitize_email($_POST['email_address'])); $post_data = array( - 'post_title' => sanitize_email($_POST['email_address']), + 'post_title' => $post_title, 'post_type' => 'email-account', 'post_status' => 'publish' ); @@ -36,31 +37,47 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['email_submit']) && is $new_post = false; $message = ['status' => 'success', 'message' => 'Email account updated successfully.']; } else { - $post_id = wp_insert_post($post_data); - $message = ['status' => 'success', 'message' => 'Email account added successfully.']; - } + // Prepare WP_Query to check for an existing post with the same title + $args = [ + 'post_type' => 'email-account', // Replace with your post type + 'post_status' => 'any', + 'title' => $post_title, + 'fields' => 'id', + 'posts_per_page' => 1 + ]; + + $query = new WP_Query($args); - if (!is_wp_error($post_id)) { - foreach ($meta_fields as $field) { - if (isset($_POST[$field])) { - update_post_meta($post_id, $field, sanitize_text_field($_POST[$field])); + if ($query->have_posts()) { + log_to_file("Email Account Edit template - email account {$post_title} already exists: "); + $message = ['status' => 'error', 'message' => 'Email account already exists.']; + } else { + $post_id = wp_insert_post($post_data); + $new_post = true; + $message = ['status' => 'success', 'message' => 'Email account added successfully.']; + if (!is_wp_error($post_id)) { + foreach ($meta_fields as $field) { + if (isset($_POST[$field])) { + update_post_meta($post_id, $field, sanitize_text_field($_POST[$field])); + } + } + // include new accounts in the warmup pool and set the owner_id to the creator of the post + 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); + } + + } else { + $message = ['status' => 'error', 'message' => 'Error: ' . $post_id->get_error_message()]; + } + + if ( !(isset($_POST['stay_on_page']) && ($_POST['stay_on_page'] === 'on')) ) { + // log_to_file("Email Account Edit template - stay_on_page not set; redirecting!"); + wp_redirect( get_permalink( $post_id ) ); + exit; } } - // include new accounts in the warmup pool and set the owner_id to the creator of the post - 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); - } - - } else { - $message = ['status' => 'error', 'message' => 'Error: ' . $post_id->get_error_message()]; - } - - if ( !(isset($_POST['stay_on_page']) && ($_POST['stay_on_page'] === 'on')) ) { - // log_to_file("Email Account Edit template - stay_on_page not set; redirecting!"); - wp_redirect( get_permalink( $post_id ) ); - exit; - } + } } // Get existing post data if editing diff --git a/page-email-accounts.php b/page-email-accounts.php index 4e5bd6d..2414a9f 100644 --- a/page-email-accounts.php +++ b/page-email-accounts.php @@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } -$items_per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 10; +$items_per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 25; get_header(); ?> diff --git a/single-campaign.php b/single-campaign.php index dd3b1c3..735ee23 100644 --- a/single-campaign.php +++ b/single-campaign.php @@ -25,6 +25,7 @@ get_header(); ?> while ( have_posts() ) : the_post(); + $post_id = get_the_ID(); ?> @@ -52,9 +53,15 @@ get_header(); ?> do_action( 'generate_before_entry_title' ); if ( generate_show_title() ) { - $params = generate_get_the_title_parameters(); + $params = generate_get_the_title_parameters(); + $post_id = get_the_ID(); // Get the current post ID + $edit_url = "/dashboard/campaigns/edit-campaign?edit={$post_id}"; // Construct the edit URL - the_title( $params['before'], $params['after'] ); + echo $params['before']; // Output the title before markup + echo ''; // Begin the link + the_title(); // Display the title + echo ''; // Close the link + echo $params['after']; // Output the title after markup } /** @@ -65,7 +72,9 @@ get_header(); ?> * @hooked generate_post_meta - 10 */ do_action( 'generate_after_entry_title' ); + $campaign_tracking_id = get_field('campaign_tracking_id', $post_id); ?> +