diff --git a/.gitignore b/.gitignore index 923d2fa..1d0a2e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /includes/vendor/ +composer.phar +/vendor/ diff --git a/css/admin-style.css b/css/admin-style.css index c011e53..580db56 100644 --- a/css/admin-style.css +++ b/css/admin-style.css @@ -1,19 +1,12 @@ /* Admin Meta Box Styles */ -#fix_deliverability_dns_issues_box, -#update_dkim_record_box, -#update_dmarc_record_box, -#update_spf_record_box { - background: #f9f9f9; +#side-sortables .postbox { +/* background: #0e0e0e;*/ border: 1px solid #ddd; - padding: 15px; margin-bottom: 15px; border-radius: 4px; } -#fix_deliverability_dns_issues_box h2, -#update_dkim_record_box h2, -#update_dmarc_record_box h2, -#update_spf_record_box h2 { +#side-sortables .postbox h2 { font-size: 16px; font-weight: bold; margin-bottom: 10px; @@ -32,7 +25,7 @@ button.button-primary:hover { color: #fff; } -.meta-box-sortables input, .meta-box-sortables textarea, .meta-box-sortables select, .meta-box-sortables input[type=number] { +.meta-box-sortables input[type=text], .meta-box-sortables textarea, .meta-box-sortables input[type=email], .meta-box-sortables select, .meta-box-sortables input[type=number] { width: 100%; max-width: 100%; } @@ -42,6 +35,33 @@ table.rl_admin_meta_table { table-layout: fixed; } +/* Campaign Timeline */ +.timeline-grid { + display: flex; + flex-wrap: wrap; + max-width: 100%; + margin: 10px 0; +} + +.timeline-grid .day { + flex: 0 0 14.28%; /* 7 rows = 100% / 7 columns */ + box-sizing: border-box; + border: 1px solid #ccc; + padding: 5px; + text-align: center; + font-size: 12px; + color: #000; +} + +.timeline-grid .day .date { + font-weight: bold; + margin-bottom: 5px; +} + +.timeline-grid .day .volume { + font-size: 14px; +} + #campaign-timeline-heatmap { width: 100%; /* Ensure it spans the available space */ height: 300px; /* Set a fixed height */ diff --git a/includes/class-rl-mailwarmer-campaign-helper.php b/includes/class-rl-mailwarmer-campaign-helper.php index 3c621ba..fe037b0 100644 --- a/includes/class-rl-mailwarmer-campaign-helper.php +++ b/includes/class-rl-mailwarmer-campaign-helper.php @@ -30,7 +30,7 @@ class RL_MailWarmer_Campaign_Helper explode("\n", $holidays_raw) ); - $min_starting_volume = (int) get_field('min_starting_email_volume', 'option') ?: 10; + $min_starting_volume = (int) get_field('min_starting_email_volume', 'option') ?: 5; $max_daily_volume = (int) get_field('max_campaign_daily_email_volume', 'option') ?: 1000; // Calculate starting daily volume (2.5% of target volume) @@ -41,8 +41,12 @@ class RL_MailWarmer_Campaign_Helper $total_days = $warmup_period * 7; // Total days in the campaign $start_date = new DateTime($start_date); + + // Calculate daily ramp-up rate - $daily_increase = ($target_volume - $starting_daily_volume) / $total_days; + $daily_increase = ($target_volume - $starting_daily_volume) / ($total_days * .75); + + // log_to_file("calculate_campaign_timeline - Ramping up from $min_starting_volume to $target_volume over $total_days days, increasing by $daily_increase each day with no more than $max_daily_volume emails in a day"); // Generate timeline for ($day = 0; $day < $total_days; $day++) { @@ -53,193 +57,907 @@ class RL_MailWarmer_Campaign_Helper // Adjust for holidays and weekends $is_weekend = in_array($current_date->format('N'), [6, 7]); $is_holiday = in_array($date_formatted, $holidays); - $reduction_factor = ($is_weekend || $is_holiday) ? rand(65, 82) / 100 : 1; + $reduction_factor = ($is_weekend || $is_holiday) ? mt_rand(65, 82) / 100 : 1; // Calculate daily volume $daily_volume = min( ceil(($starting_daily_volume + ($daily_increase * $day)) * $reduction_factor), - $target_volume * 1.2 + ceil($target_volume + ($target_volume * mt_rand(5,20))/100) ); + + /*$daily_volume = ceil(($starting_daily_volume + ($daily_increase * $day)) * $reduction_factor); + if ($daily_volume >= $target_volume) { + $daily_volume = ceil( $target_volume + ($target_volume * mt_rand(5,20))/100 ); + }*/ + if ($daily_volume > $max_daily_volume) { + log_to_file("calculate_campaign_timeline - Max Daily Volume hit for campaign $campaign_id! Capping number of emails"); $daily_volume = $max_daily_volume; } - $timeline[$date_formatted] = $daily_volume; + $timeline[$date_formatted] = [ 'target_volume' => $daily_volume, 'current_volume' => 0, 'items_sent' => 0 ]; + // log_to_file("calculate_campaign_timeline - $day: $daily_volume"); + // array_push($timeline, ) + // $timeline[$date_formatted] = $daily_volume; } // Save the timeline as a JSON string to the campaign post - $timeline_json = json_encode($timeline); - // log_to_file("Timeline JSON: $timeline_json"); - update_post_meta($campaign_id, 'campaign_timeline', $timeline_json); + // $timeline_json = json_encode($timeline); + // update_post_meta($campaign_id, 'campaign_timeline', $timeline_json); + + // log_to_file("calculate_campaign_timeline - Empty Timeline: $timeline_json"); + $filled_timeline = self::fill_campaign_timeline($campaign_id, $timeline); + log_to_file("calculate_campaign_timeline - Filled Timeline: ", $filled_timeline); + + // Check the number of saved messages per date + $message_counts = RL_MailWarmer_DB_Helper::get_message_counts_by_date($campaign_id); + // log_to_file("fill_campaign_timeline - Message counts: ", $message_counts); + + // Save the updated campaign timeline + update_post_meta($campaign_id, 'campaign_timeline', json_encode($filled_timeline, JSON_PRETTY_PRINT)); + update_post_meta($campaign_id, 'message_counts', json_encode($message_counts, JSON_PRETTY_PRINT)); + + return $timeline; } - /** - * Generate a conversation for a campaign. - * - * @param int $campaign_id The ID of the campaign. - * @param array $conversation_steps Blueprint of the conversation (participants, replies, etc.). - * @param string $prompt AI prompt for generating email content. - * - * @return int|WP_Error The ID of the created conversation or a WP_Error on failure. - */ - public static function generate_conversation($campaign_id, $conversation_steps, $prompt) { - if (empty($campaign_id) || empty($conversation_steps)) { - return new WP_Error('invalid_data', __('Invalid campaign or conversation steps.', 'rl-mailwarmer')); + + public static function fill_campaign_timeline(int $campaign_id, array $campaign_timeline): array { + $filled_dates = []; + $weekly_volumes = []; + $current_week = ''; + $weekly_total = 0; + $ratios = [ + 'extra-long' => [ + 'percent_of_volume_lower' => 0, + 'percent_of_volume_upper' => 0, + 'num_participants_lower' => 2, + 'num_participants_upper' => 8, + 'num_responses_lower' => 5, + 'num_responses_upper' => 12 + ], + 'long' => [ + 'percent_of_volume_lower' => 2, + 'percent_of_volume_upper' => 4, + 'num_participants_lower' => 3, + 'num_participants_upper' => 6, + 'num_responses_lower' => 4, + 'num_responses_upper' => 6 + ], + 'medium' => [ + 'percent_of_volume_lower' => 2, + 'percent_of_volume_upper' => 6, + 'num_participants_lower' => 3, + 'num_participants_upper' => 5, + 'num_responses_lower' => 3, + 'num_responses_upper' => 5 + ], + 'short' => [ + 'percent_of_volume_lower' => 15, + 'percent_of_volume_upper' => 20, + 'num_participants_lower' => 2, + 'num_participants_upper' => 4, + 'num_responses_lower' => 2, + 'num_responses_upper' => 4 + ], + ]; + $warmup_period = (int) get_post_meta($campaign_id, 'warmup_period', true); // Weeks + $start_date = get_post_meta($campaign_id, 'start_date', true); // Campaign start date + $total_days = $warmup_period * 7; // Total days in the campaign + $start_date = date('Y-m-d H:i:s', strtotime($start_date)); + $end_date = date('Y-m-d 23:59:59', strtotime($start_date . " + {$total_days} days")); + // $end_date = date('Y-m-d H:i', strtotime($start_date . " +{$total_days} days")); + log_to_file("fill_campaign_timeline - Start: $start_date End: $end_date"); + + // // Calculate weekly volumes + // foreach ($campaign_timeline as $date => $data) { + // $week = date('Y-W', strtotime($date)); + // if ($week !== $current_week) { + // if ($current_week !== '') { + // $weekly_volumes[$current_week] = $weekly_total; + // } + // $current_week = $week; + // $weekly_total = 0; + // } + // $weekly_total += $data['target_volume']; + // if (next($campaign_timeline) === false) { + // $weekly_volumes[$week] = $weekly_total; + // } + // } + // Calculate weekly volumes + foreach ($campaign_timeline as $date => $data) { + $timestamp = strtotime($date); + $year = date('o', $timestamp); // ISO year + $week = date('W', $timestamp); // ISO week + $week_key = sprintf('%d-%02d', $year, $week); + + if (!isset($weekly_volumes[$week_key])) { + $weekly_volumes[$week_key] = 0; + } + $weekly_volumes[$week_key] += $data['target_volume']; } - // Insert the conversation into the database + // Process each week + foreach ($weekly_volumes as $week => $volume) { + log_to_file("fill_campaign_timeline - Week $week goal: $volume"); + $weekly_scheduled_messages = 0; + + foreach ($ratios as $length => $ratio) { + // Calculate number of conversations for this length + $percent = mt_rand($ratio['percent_of_volume_lower'], $ratio['percent_of_volume_upper']) / 100; + $num_conversations = ceil($volume * $percent); + log_to_file("fill_campaign_timeline - Generating $num_conversations $length conversations"); + + // Skip if no conversations to generate + if ($num_conversations === 0) continue; + + // Generate conversations + for ($i = 0; $i < $num_conversations; $i++) { + // Check if weekly volume reached + if ($weekly_scheduled_messages >= $volume) { + break 2; // Break both loops + } + + // Generate random number of responses for this conversation + $num_responses = mt_rand($ratio['num_responses_lower'], $ratio['num_responses_upper']); + + // Get week start date + list($year, $weekNum) = explode('-', $week); + $week_start = date('Y-m-d', strtotime($year . 'W' . $weekNum)); + + if ($week_start < $start_date) { + $week_start = $start_date; + } + + // Generate conversation blueprint + $conversation_steps = self::generate_conversation_blueprint( + $campaign_id, + $num_responses, + $week_start, + $end_date, + $filled_dates + ); + + // Update timeline volumes + foreach ($conversation_steps as $step) { + $step_date = date('Y-m-d', strtotime($step['scheduled_for'])); + if (isset($campaign_timeline[$step_date])) { + $campaign_timeline[$step_date]['current_volume']++; + $weekly_scheduled_messages++; + + // Check if date is now filled + if ($campaign_timeline[$step_date]['current_volume'] >= + $campaign_timeline[$step_date]['target_volume']) { + $filled_dates[] = $step_date; + } + } + } + } + } + } + + // log_to_file("fill_campaign_timeline - Campaign Timeline without single-step conversations: ", $campaign_timeline); + + // Fill remaining capacity with single-message conversations + log_to_file("fill_campaign_timeline - Filling remaining days with single conversations"); + foreach ($campaign_timeline as $date => $data) { + $remaining = $data['target_volume'] - $data['current_volume']; + // log_to_file("fill_campaign_timeline - $date remaining: $remaining"); + + while ($remaining > 0) { + $conversation_steps = self::generate_conversation_blueprint( + $campaign_id, + 0, + $date, + $date + ); + + foreach ($conversation_steps as $step) { + $step_date = date('Y-m-d', strtotime($step['scheduled_for'])); + if (isset($campaign_timeline[$step_date])) { + $campaign_timeline[$step_date]['current_volume']++; + $remaining--; + + if ($campaign_timeline[$step_date]['current_volume'] >= + $campaign_timeline[$step_date]['target_volume']) { + $filled_dates[] = $step_date; + } + } + } + } + } + + return $campaign_timeline; + } + + + + + + + /** + * Generate a conversation blueprint for a campaign. + * + * @param int $campaign_id The ID of the campaign. + * @param int $number_responses The number of responses/messages to schedule. Defaults to 0. + * @return int The ID of the created conversation in the database. + * @throws Exception If required data is missing or saving fails. + */ + public static function generate_conversation_blueprint($campaign_id, $number_responses = 0, $start_date = false, $end_date = false, $filled_dates = []) { + global $wpdb; + + // Fetch the start date for the campaign if one isn't passed + if (!$start_date) { + $start_date = get_post_meta($campaign_id, 'start_date', true); + if (!$start_date) { + throw new Exception(__('generate_conversation_blueprint - Campaign start date is missing.', 'rl-mailwarmer')); + } + } + // log_to_file("generate_conversation_blueprint - Generating conversation with starting date: $start_date & end date: $end_date"); + + + // Step 1: Generate placeholders for the conversation steps + $array_args = ['step' => '', 'status' => 'scheduled']; + $conversation_steps = array_fill(0, $number_responses + 1, $array_args); + $conversation_steps = self::add_timestamps_to_conversation($conversation_steps, $start_date, $end_date, $filled_dates); + // log_to_file("generate_conversation_blueprint - Conversation Steps", $conversation_steps); + + + // AI Prompt Defaults + $args = [ + 'initiated_by' => null, + 'received_by' => null, + 'subject' => null, + 'length' => null, + 'num_participants' => null, + 'num_responses' => 0, + 'reply_pool' => [], + 'cc_pool' => [], + ]; + // $args = wp_parse_args($args, $defaults); + $args['num_responses'] = $number_responses; + + // Fetch campaign target profession + // log_to_file("generate_conversation_blueprint - Target Profession"); + $target_profession = get_field('target_profession', $campaign_id); + + // log_to_file("generate_conversation_blueprint - From Email"); + $from_emails = get_post_meta($campaign_id, 'email_accounts', true); + if (!$from_emails) { + throw new Exception(__('generate_conversation_blueprint - from_email is missing.', 'rl-mailwarmer')); + } + $from_pool = []; + foreach ($from_emails as $email_id) { + $from_pool[] = get_the_title($email_id); + } + // $email = get_post($from_email); + log_to_file("generate_conversation_blueprint - From pool: ", $from_pool); + + // Fetch scrubber pool if 'received_by' is not passed + // log_to_file("generate_conversation_blueprint - Scrubber Pool"); + if (!$args['received_by']) { + $args['received_by'] = get_field('scrubber_pool', 'option') ? rl_get_textarea_meta_as_array('option', 'options_scrubber_pool') : ''; + // $args['received_by'] = explode(',', $scrubber_pool); + } + + // Fetch the CC Pool + // log_to_file("generate_conversation_blueprint - CC Pool"); + $cc_pool = self::get_email_accounts_for_pool($campaign_id, 'cc'); + // Get up to 4 random items + $num_to_select = min(4, count($cc_pool)); // Ensure we don't request more items than are available + $random_keys = array_rand($cc_pool, $num_to_select); + + // If only one item is selected, ensure it's returned as an array + // $args['cc_pool'] = is_array($random_keys) ? array_intersect_key($cc_pool, array_flip($random_keys)) : [$cc_pool[$random_keys]]; + + + // log_to_file("generate_conversation_blueprint - Reply Pool"); + // Fetch the Reply pool if there will be any replies needed + if ( intval($args['num_responses']) > 0) { + $reply_pool = self::get_email_accounts_for_pool($campaign_id, 'reply'); + $args['reply_pool'] = array_values(array_intersect($args['received_by'], $reply_pool)); + } + + + + // log_to_file("generate_conversation_blueprint - Conversation Topics"); + // Fetch topics for the conversation if 'subject' is not passed + if (!$args['subject']) { + $default_topics = get_option('options_default_topic_pool') ? rl_get_textarea_meta_as_array("option", "default_topic_pool") : []; + $campaign_topics = get_post_meta($campaign_id, 'campaign_conversation_topics', true) ? rl_get_textarea_meta_as_array($campaign_id, 'campaign_conversation_topics') : []; + // $topics_2 = get_post_meta($campaign_id, 'campaign_conversation_topics', true); + // log_to_file("generate_conversation_blueprint - Default topics:", $default_topics); + // log_to_file("generate_conversation_blueprint - Campaign topics:", $campaign_topics); + // log_to_file("generate_conversation_blueprint - Campaign topics2 : $topics_2"); + + $all_topics = array_merge($default_topics, $campaign_topics); + // $all_topics_count = count($all_topics); + // log_to_file("generate_conversation - All topics $all_topics_count: ", $all_topics); + if (!empty($all_topics)) { + $args['subject'] = $all_topics[array_rand($all_topics)]; + } else { + $args['subject'] = __('General Inquiry', 'rl-mailwarmer'); + } + } + + // log_to_file("generate_conversation_blueprint - Prompt"); + // Generate the prompt + $prompt = sprintf( + "Generate a JSON email conversation with distinct participant personalities; up to 5%% errors; initiating email can be sent to multiple people; replies and follow-ups only from the sender or addresses in both 'can_reply' AND 'to_pool'. Include only: from, to, cc, subject, body. Don't include signatures Return only JSON, no notes\n%s", + json_encode([ + 'profession' => $target_profession, + 'from_pool' => $from_pool, + 'to_pool' => $args['received_by'], + 'subject' => $args['subject'], + 'num_of_replies' => $args['num_responses'], + 'can_reply' => $args['reply_pool'], + 'available_to_cc' => $args['cc_pool'], + ]) + ); + + // log_to_file("From prompt: ", $prompt); + + // Step 2: Save the conversation to the database + $conversation_data = [ + 'campaign_ID' => $campaign_id, + 'created_at' => current_time('mysql'), + 'status' => 'new', + 'first_message_timestamp' => $conversation_steps[0]['scheduled_for'], + 'prompt' => $prompt, + 'conversation_steps' => json_encode($conversation_steps), + ]; + + $conversation_id = RL_MailWarmer_DB_Helper::insert_conversation($conversation_data); + // $conversation_id = 69; + + // $conversation_table = $wpdb->prefix . 'rl_mailwarmer_conversation'; + + // $wpdb->insert($conversation_table, $conversation_data); + // $conversation_id = $wpdb->insert_id; + + if (!$conversation_id) { + throw new Exception(__('generate_conversation_blueprint - Failed to save the conversation blueprint.', 'rl-mailwarmer')); + } + + // Step 3: Save the individual message placeholders to the messages table + // $message_table = $wpdb->prefix . 'rl_mailwarmer_messages'; + foreach ($conversation_steps as $step) { + $message_data = [ + 'campaign_ID' => $campaign_id, + 'conversation_ID' => $conversation_id, + 'scheduled_for_timestamp' => $step['scheduled_for'], + 'status' => 'pending', + ]; + $message_id = RL_MailWarmer_DB_Helper::insert_message($message_data); + if (!$message_id) { + throw new Exception(__('generate_conversation_blueprint - Failed to save the message blueprint.', 'rl-mailwarmer')); + } + } + + return $conversation_steps; + } + + + /** + * Generate a single conversation for a campaign + * + * @param int $campaign_id The ID of the campaign. + * @param array $args Optional arguments to customize the conversation. + * @return int|WP_Error The ID of the created conversation or WP_Error on failure. + */ + public static function generate_conversation($campaign_id, $args = []) { + if (empty($campaign_id)) { + return new WP_Error('invalid_campaign', __('Campaign ID is required.', 'rl-mailwarmer')); + } + + // Fetch campaign details + $campaign = get_post($campaign_id); + if (!$campaign || $campaign->post_type !== 'campaign') { + return new WP_Error('invalid_campaign', __('Invalid campaign.', 'rl-mailwarmer')); + } + + // Defaults + $defaults = [ + 'initiated_by' => null, + 'received_by' => null, + 'subject' => null, + 'length' => null, + 'num_participants' => null, + 'num_responses' => null, + 'reply_pool' => [], + 'cc_pool' => [], + ]; + $args = wp_parse_args($args, $defaults); + + // Fetch email accounts for this campaign's domain if 'initiated_by' is not passed + if (!$args['initiated_by']) { + $campaign_domain = get_field('domain', $campaign_id); + $email_accounts = get_posts([ + 'post_type' => 'email-account', + 'meta_query' => [ + [ + 'key' => 'domain', + 'value' => $campaign_domain->ID, + 'compare' => '=', + ], + ], + 'posts_per_page' => -1, + 'fields' => 'ids', + ]); + if (empty($email_accounts)) { + return new WP_Error('no_initiator', __('No email accounts available for the campaign domain.', 'rl-mailwarmer')); + } + $args['initiated_by'] = $email_accounts[array_rand($email_accounts)]; + } + + // fetch the email address + // $from_id = $args['initiated_by']; + // $from_email = get_the_title($from_id); + // $args['initiated_by'] = $from_email; + + // Set length defaults for participants and responses + if ($args['length']) { + $length_defaults = [ + 'extra-long' => ['num_participants' => mt_rand(2, 8), 'num_responses' => mt_rand(5, 8)], + 'long' => ['num_participants' => mt_rand(3, 6), 'num_responses' => mt_rand(4, 6)], + 'medium' => ['num_participants' => mt_rand(3, 5), 'num_responses' => mt_rand(3, 5)], + 'short' => ['num_participants' => mt_rand(2, 4), 'num_responses' => mt_rand(1, 3)], + 'single' => ['num_participants' => mt_rand(2, 6), 'num_responses' => 0], + ]; + if (isset($length_defaults[$args['length']])) { + $defaults = $length_defaults[$args['length']]; + // log_to_file("Length defaults: ", $defaults['num_participants']); + if (isset($args['num_participants'])) { + unset($defaults['num_participants']); + } + if (isset($args['num_responses'])) { + unset($defaults['num_responses']); + } + $args = array_merge($args, $length_defaults[$args['length']]); + } + } else { + if (!$args['num_participants']) { + $args['num_participants'] = 2; + } + if (!$args['num_responses']) { + $args['num_responses'] = 0; + } + } + + // Fetch topics for the conversation if 'subject' is not passed + if (!$args['subject']) { + $default_topics = get_option('options_default_topic_pool') ? rl_get_textarea_meta_as_array("option", "default_topic_pool") : []; + $campaign_topics = get_post_meta($campaign_id, 'campaign_conversation_topics', true) ? rl_get_textarea_meta_as_array($campaign_id, 'campaign_conversation_topics') : []; + $all_topics = array_merge($default_topics, $campaign_topics); + // $all_topics_count = count($all_topics); + // log_to_file("generate_conversation - All topics $all_topics_count: ", $all_topics); + if (!empty($all_topics)) { + $args['subject'] = $all_topics[array_rand($all_topics)]; + } else { + $args['subject'] = __('General Inquiry', 'rl-mailwarmer'); + } + } + + // Fetch campaign duration + $start_date = get_field('start_date', $campaign_id); + $warmup_period = get_field('warmup_period', $campaign_id); + $end_date = date('Ymd', strtotime("+{$warmup_period} weeks", strtotime($start_date))); + + // Fetch scrubber pool if 'received_by' is not passed + if (!$args['received_by']) { + $args['received_by'] = get_field('scrubber_pool', 'option') ? rl_get_textarea_meta_as_array('option', 'options_scrubber_pool') : ''; + // $args['received_by'] = explode(',', $scrubber_pool); + } + + // Fetch the CC Pool + $args['cc_pool'] = self::get_email_accounts_for_pool($campaign_id, 'cc'); + + // Fetch the Reply pool if there will be any replies needed + if ( intval($args['num_responses']) > 0) { + $args['reply_pool'] = self::get_email_accounts_for_pool($campaign_id, 'reply'); + } + + // Fetch campaign target profession + $target_profession = get_field('target_profession', $campaign_id); + + // Generate the prompt + $prompt = sprintf( + "Generate a JSON email conversation with distinct participant personalities, up to 5%% errors, and replies only from the sender or addresses in both 'can_reply' and 'to_pool'. Include: from, to, cc, subject, body\n%s", + json_encode([ + 'profession' => $target_profession, + 'from' => $args['initiated_by'], + 'to_pool' => $args['received_by'], + 'subject' => $args['subject'], + 'num_of_replies' => $args['num_responses'], + 'num_of_participants' => $args['num_participants'], + 'can_reply' => $args['reply_pool'], + 'available_to_cc' => $args['cc_pool'], + ], JSON_PRETTY_PRINT) + ); + + log_to_file("generate_conversation - Prompt: $prompt"); + + // Save the conversation to the database + $conversation_steps = []; // Placeholder until AI generates steps $conversation_id = RL_MailWarmer_DB_Helper::insert_conversation($campaign_id, $conversation_steps, $prompt); if (!$conversation_id) { return new WP_Error('db_error', __('Failed to create conversation.', 'rl-mailwarmer')); } + // Generate AI content for the conversation + // $ai_response = self::clean_ai_response(self::generate_ai_conversation($prompt)); + $ai_response = self::clean_ai_response(get_post_meta($campaign_id, 'last_ai_response', true)); + + if (is_wp_error($ai_response)) { + return $ai_response; // Handle AI generation failure gracefully + } + + + // Update the conversation with generated steps + self::update_conversation_steps($conversation_id, $ai_response); + + // log_to_file("generate_conversation - AI response: $ai_response"); + // update_post_meta($campaign_id, 'last_ai_response', wp_slash($ai_response)); + + // Parse the AI response and save messages + // $parsed_steps = self::parse_ai_response($ai_response); + $array_args = ['step' => '']; + $parsed_steps = array_fill(0, $args['num_responses'], $array_args ); + + // Save each timestamp into the parsed steps + $parsed_steps = self::add_timestamps_to_conversation($parsed_steps, $start_date); + + log_to_file("generate_conversation - parsed_steps: ", $parsed_steps); + foreach ($parsed_steps as $index => $step) { + if (!empty($step['scheduled_for'])) { + $is_first_message = ($index === 0); + if (is_array($step['to'])) { + $step['to'] = implode(', ', $step['to']); + } + if (is_array($step['cc'])) { + $step['cc'] = implode(', ', $step['cc']); + } + // log_to_file("generate_conversation - message body: ", $step['body']); + + // RL_MailWarmer_DB_Helper::insert_message( + // $campaign_id, + // $conversation_id, + // $step['scheduled_for'], + // $step['from'], + // $step['to'], + // $step['cc'], + // $step['subject'], + // $step['body'], + // $is_first_message + // ); + } + } + + return $conversation_id; } + + /** - * Generate messages for a conversation. + * Generate AI content for the conversation using OpenAI's ChatGPT API. * - * @param int $conversation_id The ID of the conversation. - * @param int $campaign_id The ID of the campaign. - * @param array $steps The steps of the conversation. - * - * @return array An array of message IDs or WP_Error on failure. + * @param string $prompt The prompt to send to ChatGPT. + * @return string|WP_Error The AI response or WP_Error on failure. */ - public static function generate_messages($conversation_id, $campaign_id, $steps) { - if (empty($conversation_id) || empty($campaign_id) || empty($steps)) { - return new WP_Error('invalid_data', __('Invalid conversation or steps.', 'rl-mailwarmer')); + public static function generate_ai_conversation($prompt) { + $api_key = getenv('OPENAI_API_KEY'); + + if (empty($api_key)) { + return new WP_Error('missing_api_key', __('OpenAI API key is not configured.', 'rl-mailwarmer')); } - $message_ids = []; + try { + $client = \OpenAI::client($api_key); + $response = $client->chat()->create([ + 'model' => 'gpt-4o', + 'messages' => [ + ['role' => 'system', 'content' => 'You are a helpful assistant specialized in generating email conversations.'], + ['role' => 'user', 'content' => $prompt], + ], + 'temperature' => 0.7, + 'max_tokens' => 2000, + ]); - foreach ($steps as $step) { - $scheduled_for = $step['scheduled_for']; - $from_email = $step['from']; - $to_email = implode(',', $step['to']); - $cc = isset($step['cc']) ? implode(',', $step['cc']) : null; - $subject = $step['subject']; - $body = $step['body']; - - // Insert the message into the database - $message_id = RL_MailWarmer_DB_Helper::insert_message( - $campaign_id, - $conversation_id, - $scheduled_for, - $from_email, - $to_email, - $cc, - $subject, - $body - ); - - if (!$message_id) { - return new WP_Error('db_error', __('Failed to create message.', 'rl-mailwarmer')); - } - - $message_ids[] = $message_id; + return $response['choices'][0]['message']['content']; + } catch (\Exception $e) { + return new WP_Error('api_error', $e->getMessage()); } - - return $message_ids; } /** - * Generate conversations and messages for a campaign. + * Clean the AI response by removing Markdown-style code block delimiters. + * + * @param string $response The raw AI response. + * @return string The cleaned JSON string. + */ + public static function clean_ai_response($response) { + // Use regex to remove the ```json and ``` delimiters + return preg_replace('/^```json\s*|```$/m', '', trim($response)); + } + + /** + * Parse the AI response into structured steps for message creation. + * + * @param string $ai_response The JSON string returned by ChatGPT. + * @return array An array of parsed conversation steps. + */ + public static function parse_ai_response($ai_response) { + $steps = json_decode($ai_response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return new WP_Error('invalid_response', __('Failed to parse AI response.', 'rl-mailwarmer')); + } + + return $steps; + } + + /** + * Fetch all published email accounts matching the campaign domain with "include_in_cc_pool" set to true. * * @param int $campaign_id The ID of the campaign. - * @param int $weekly_volume The target weekly email volume. - * - * @return array An array of created conversation IDs or WP_Error on failure. + * @param string $pool The name of the pool. Valid options are: cc, reply + * @return array The email addresses to include in the CC pool. */ - public static function generate_conversations($campaign_id, $weekly_volume) { - $conversation_ids = []; + private static function get_email_accounts_for_pool($campaign_id, $pool) { + // Get the domain associated with the campaign + $domain_id = get_post_meta($campaign_id, 'domain', true); - // Example: Break down weekly volume into conversation ratios - $conversation_ratios = [ - ['percentage' => 5, 'participants' => [3, 6], 'replies' => [5, 12]], - ['percentage' => 10, 'participants' => [3, 5], 'replies' => [3, 5]], - ['percentage' => 15, 'participants' => [2, 4], 'replies' => [1, 3]], - ['percentage' => 70, 'participants' => [2, 6], 'replies' => [0, 0]], - ]; + if (empty($domain_id)) { + return []; // Return an empty array if no domain is found + } - foreach ($conversation_ratios as $ratio) { - $volume = ceil($weekly_volume * ($ratio['percentage'] / 100)); + // Query email accounts + if ($pool === 'reply') { + $args = [ + 'post_type' => 'email-account', + 'post_status' => 'publish', + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => 'include_in_' . $pool . '_pool', + 'value' => true, + 'compare' => '=', + ], + // [ + // 'key' => 'domain', + // 'value' => $domain_id, + // 'compare' => '=', + // ], + ], + 'fields' => 'ids', // Retrieve only IDs for efficiency + 'posts_per_page' => -1, // Fetch all matching posts + ]; + } else if ($pool === 'cc') { + + $args = [ + 'post_type' => 'email-account', + 'post_status' => 'publish', + 'meta_query' => [ + 'relation' => 'AND', + [ + 'key' => 'include_in_' . $pool . '_pool', + 'value' => true, + 'compare' => '=', + ], + // [ + // 'key' => 'domain', + // 'value' => $domain_id, + // 'compare' => '=', + // ], + ], + 'fields' => 'ids', // Retrieve only IDs for efficiency + 'posts_per_page' => -1, // Fetch all matching posts + ]; + } - for ($i = 0; $i < $volume; $i++) { - $conversation_steps = []; // Generate steps based on ratio - $prompt = ''; // Generate prompt for AI (to be implemented) + $query = new WP_Query($args); - // Create conversation - $conversation_id = self::generate_conversation($campaign_id, $conversation_steps, $prompt); + $email_pool = []; - if (is_wp_error($conversation_id)) { - return $conversation_id; + if ($query->have_posts()) { + foreach ($query->posts as $post_id) { + $email_address = get_the_title($post_id); + if (!empty($email_address)) { + $email_pool[] = $email_address; } - - $steps = []; // Generate steps for this conversation - $message_result = self::generate_messages($conversation_id, $campaign_id, $steps); - - if (is_wp_error($message_result)) { - return $message_result; - } - - $conversation_ids[] = $conversation_id; } } - return $conversation_ids; - } - - - /** - * Generate additional email accounts for the campaign. - * - * Creates additional email accounts for initiating email conversations, - * replying to chains, and being included on CCs to increase email volume. - * - * @param int $campaign_id The campaign post ID. - * @return array List of generated email accounts. - */ - public static function generate_campaign_email_accounts($campaign_id) - { - // Implementation placeholder - return []; + return $email_pool; } /** - * Generate conversation vectors for the campaign. + * Add timestamps to parsed conversation steps. * - * Creates a detailed map of email chains, participants, and timelines, - * based on campaign requirements. - * - * @param int $campaign_id The campaign post ID. - * @return array List of conversation vectors with timestamps and participants. + * @param array $parsed_steps The parsed steps of the conversation. + * @param string $start_date The starting date for the timestamps. + * @return array The updated conversation steps with timestamps and step indices. */ - public static function generate_campaign_conversation_vectors($campaign_id) - { - // Implementation placeholder - return []; + public static function add_timestamps_to_conversation($parsed_steps, $start_date, $end_date = NULL, $filled_dates = []) { + // Ensure parsed_steps is an array + if (!is_array($parsed_steps) || empty($parsed_steps)) { + throw new Exception(__('Parsed steps must be a non-empty array.', 'rl-mailwarmer')); + } + + // Initialize the updated steps array + $updated_steps = []; + + // Iterate through each step and assign timestamps + foreach ($parsed_steps as $index => $step) { + + // If this is not the first step, ensure the start_date is at least 5 minutes after the previous step + if ($index > 0) { + $previous_timestamp = strtotime($updated_steps[$index - 1]['scheduled_for']); + $start_date = date('Y-m-d H:i:s', max($timestamp, $previous_timestamp + 300)); // 300 seconds = 5 minutes + } + + // Use the helper function to generate a random timestamp + $timestamp = self::generate_random_date_time($start_date, $end_date, $filled_dates ); + // log_to_file("add_timestamps_to_conversation - Returned random timestamp: $timestamp"); + + // Update the step with the scheduled timestamp and step index + $step['scheduled_for'] = date('Y-m-d H:i:s', $timestamp); + $step['step'] = $index + 1; + + // Update the starting date for the next iteration + $start_date = date('Y-m-d H:i:s', $timestamp); + + // Append the updated step + $updated_steps[] = $step; + } + // log_to_file("add_timestamps_to_conversation - Updated steps: ", $updated_steps); + + return $updated_steps; } /** - * Generate email conversation content using ChatGPT. + * Generate a semi-randomized timestamp with a random date and time. * - * Creates realistic email conversations with context and replies, - * leveraging ChatGPT for natural language generation. - * - * @param int $campaign_id The campaign post ID. - * @param array $vector Details of the conversation vector (participants, topics, etc.). - * @return string Generated email conversation content. + * @param string $start_date The start date in 'Y-m-d' format. Defaults to today. + * @param string $end_date The end date in 'Y-m-d' format. Defaults to today + 7 days. + * @param string $start_time The start time in 'H:i' format (e.g., '08:00'). Defaults to '08:00'. + * @param string $end_time The end time in 'H:i' format (e.g., '18:00'). Defaults to '18:00'. + * @return int The random timestamp. */ - public static function generate_email_conversation($campaign_id, $vector) - { - // Implementation placeholder - return ''; + private static function generate_random_date_time($start_date = null, $end_date = null, $filled_dates = [], $start_time = '07:20', $end_time = '21:00' ) { + // Default dates + $start_date = $start_date ?: date('Y-m-d H:i'); + $end_date = $end_date ?: date('Y-m-d H:i', strtotime($start_date . ' +8 days')); + + // log_to_file("generate_random_date_time - Generating random date time between $start_date & $end_date"); + + $start_date_timestamp = strtotime($start_date); + $end_date_timestamp = strtotime($end_date); + $retry = false; + + if ($end_date_timestamp < $start_date_timestamp) { + + throw new Exception('generate_random_date_time - End date must be greater than or equal to start date.'); + + } else if ( $end_date_timestamp === $start_date_timestamp ) { + + // Single-part conversations have the same start & end date + + $random_date = date('Y-m-d', $start_date_timestamp); + // log_to_file("generate_random_date_time - Using the same start & end date"); + + } else { + + // Generate a random date within the timeframe + // log_to_file("generate_random_date_time - Finding a date that's not in \$filled_dates"); + do { + + // if ($retry) { + // // log_to_file("generate_random_date_time - Regenerating random date because it was found in filled_dates."); + + // } + + // Generate a random date between the start and end dates + $random_date_timestamp = mt_rand($start_date_timestamp, $end_date_timestamp); + $random_date = date('Y-m-d', $random_date_timestamp); + + // $retry = true; + // log_to_file("generate_random_date_time - Random date $random_date"); + // log_to_file("generate_random_date_time - Filled Dates: ", $filled_dates); + } while (in_array($random_date, $filled_dates)); + // log_to_file("generate_random_date_time - Random date found!"); + } + + + + + // Generate a random time within the specified range for the selected date + // log_to_file("generate_random_date_time - calling generate_random_time( $random_date, $start_time, $end_time)"); + $random_time_timestamp = self::generate_random_time($random_date, $start_time, $end_time); + // log_to_file("generate_random_date_time - Returned random timestamp: $random_time_timestamp"); + + return $random_time_timestamp; } + + /** + * Generate a random timestamp for a given date between specific hours. + * + * @param string $date The date in 'Y-m-d' format. + * @param string $start_time The start time in 'H:i' format. + * @param string $end_time The end time in 'H:i' format. + * @return int The random timestamp. + */ + private static function generate_random_time($date, $start_time, $end_time) { + // Convert start and end times to timestamps + $start_timestamp = strtotime("$date $start_time"); + $end_timestamp = strtotime("$date $end_time"); + // log_to_file("generate_random_time - Start: $start_timestamp End: $end_timestamp"); + + if ($end_timestamp < $start_timestamp) { + throw new Exception('generate_random_time - End time must be greater or equal to start time.'); + } + + // Generate a random timestamp within the time range + $timestamp = mt_rand($start_timestamp, $end_timestamp); + // log_to_file("generate_random_time - Random Timestamp: $timestamp"); + return $timestamp; + } + + + + + + /** + * Update the conversation steps for a given conversation ID. + * + * @param int $conversation_id The ID of the conversation to update. + * @param string $ai_response The AI-generated response in JSON format. + * @return bool|WP_Error True on success, WP_Error on failure. + */ + public static function update_conversation_steps($conversation_id, $ai_response) { + global $wpdb; + // log_to_file("update_conversation_steps - Running! $conversation_id ", $ai_response); + + // Validate input + if (empty($conversation_id) || !is_int($conversation_id)) { + return new WP_Error('invalid_conversation_id', __('Invalid conversation ID.', 'rl-mailwarmer')); + } + + if (empty($ai_response) || !is_string($ai_response)) { + return new WP_Error('invalid_ai_response', __('Invalid AI response.', 'rl-mailwarmer')); + } + + // Define the table name + $table_name = $wpdb->prefix . 'rl_mailwarmer_conversation'; + + // Prepare the data + $data = [ + 'conversation_steps' => $ai_response, // Save the JSON response as a string + ]; + $where = [ + 'id' => $conversation_id, + ]; + + // Update the database + $result = $wpdb->update($table_name, $data, $where, ['%s'], ['%d']); + + if ($result === false) { + return new WP_Error('db_update_failed', __('Failed to update the conversation steps.', 'rl-mailwarmer')); + } + // log_to_file("update_conversation_steps - Updated conversation steps for #$conversation_id"); + return true; + } + + + + + + } /** @@ -322,89 +1040,448 @@ add_action('wp_ajax_rl_mailwarmer_generate_timeline', function () { } }); -// Cal-Heatmap +/* + * Generate Conversation Metabox + * + */ - -add_action('edit_form_after_title', function ($post) { - if ($post->post_type === 'campaign') { - echo '
'; - } +add_action('add_meta_boxes', function () { + add_meta_box( + 'generate_conversation_box', // Unique ID for the meta box + __('Generate Conversation', 'rl-mailwarmer'), // Title of the meta box + 'rl_mailwarmer_render_conversation_box', // Callback to display the box content + 'campaign', // Post type + 'side', // Context: side, normal, or advanced + 'default' // Priority + ); }); + +/** + * Render the Generate Conversation meta box. + * + * @param WP_Post $post The current post object. + */ +function rl_mailwarmer_render_conversation_box($post) { + // Add a nonce for security + wp_nonce_field('rl_generate_conversation_nonce', 'rl_generate_conversation_nonce_field'); + + // Meta box form + ?> +' . esc_html__('No timeline available for this campaign.', 'rl-mailwarmer') . '
'; + return; + } + + $campaign_timeline = json_decode($campaign_timeline, true); + + // Organize timeline by weeks for grid structure + $weeks = []; + $max_volume = 0; + foreach ($campaign_timeline as $date => $day) { + $max_volume = max($max_volume, $day['target_volume']); + $week_number = date('W', strtotime($date)); + if (!isset($weeks[$week_number])) { + $weeks[$week_number] = []; + } + $weeks[$week_number][$date] = $day; + } + // log_to_file("edit_form_after_title - Weeks array: ", $weeks); + + // Add padding days and flatten + $grid_columns = []; + foreach ($weeks as $week_number => $week) { + // Get the first date of this week + $first_date = array_key_first($week); + $first_day_num = date('N', strtotime($first_date)); // 1 (Monday) through 7 (Sunday) + + // Add padding days before if needed + if ($first_day_num > 1) { + $padding_days = $first_day_num - 1; + $monday_date = date('Y-m-d', strtotime("-{$padding_days} days", strtotime($first_date))); + + for ($i = 0; $i < $padding_days; $i++) { + $pad_date = date('Y-m-d', strtotime("+{$i} days", strtotime($monday_date))); + $grid_columns[$pad_date] = [ + 'target_volume' => 0, + 'is_padding' => true + ]; + } + } + + // Add actual days + foreach ($week as $date => $day) { + $day['is_padding'] = false; + $grid_columns[$date] = $day; + } + } + + // log_to_file("rl_mailwarmer_display_campaign_timeline - Grid Columns: ", $grid_columns); + + $dates = array_keys($grid_columns); + $volumes = array_map(function($day) { + return (isset($day['is_padding']) && $day['is_padding'] == true ) ? 0 : $day; + }, $grid_columns); + + // log_to_file("rl_mailwarmer_display_campaign_timeline - Grid volumes: ", $volumes); + + $chart_data = array_map(function($date, $volume) { + return [ + 'date' => date('M d', strtotime($date)), + 'target' => isset($volume['target_volume']) ? $volume['target_volume'] : null , + 'sent' => isset($volume['items_sent']) ? $volume['items_sent'] : null + ]; + }, $dates, $volumes); + + // log_to_file("Chart Data: ", $chart_data); + + // Gradient colors array + // $gradients = ["#f12711", "#f24913", "#f36b15", "#f48d17", "#f5af19", "#f8c353", "#fad78c", "#fdebc6", "#ffffff"]; + $gradients = ["#ffffff", "#fdebc6", "#fad78c", "#f8c353", "#f5af19", "#f48d17", "#f36b15", "#f24913", "#f12711"]; + + + // Generate the grid + ?> + ++ + +
+ + + admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('rl_generate_accounts_nonce'), + ]); + } +} + +add_action('wp_ajax_rl_generate_random_accounts', 'rl_mailwarmer_generate_random_accounts_handler'); +function rl_mailwarmer_generate_random_accounts_handler() { + check_ajax_referer('rl_generate_accounts_nonce', 'security'); + + $post_id = intval($_POST['post_id']); + $qty = intval($_POST['qty']); + + if (!$post_id || !$qty || $qty < 1 || $qty > 50) { + wp_send_json_error(__('Invalid input.', 'rl-mailwarmer')); + } + + $accounts = RL_MailWarmer_Email_Helper::generate_random_accounts($post_id, $qty); + + if (is_array($accounts)) { + wp_send_json_success($accounts); + } else { + wp_send_json_error($accounts); + } +} diff --git a/includes/class-rl-mailwarmer-email-handler.php b/includes/class-rl-mailwarmer-email-handler.php index 9ceef04..20351ec 100644 --- a/includes/class-rl-mailwarmer-email-handler.php +++ b/includes/class-rl-mailwarmer-email-handler.php @@ -138,8 +138,8 @@ class RL_MailWarmer_Email_Handler { $start_hour = 8; // 8 AM $end_hour = 18; // 6 PM - $random_hour = rand($start_hour, $end_hour - 1); - $random_minute = rand(0, 59); + $random_hour = mt_rand($start_hour, $end_hour - 1); + $random_minute = mt_rand(0, 59); return strtotime(date('Y-m-d', $timestamp) . " {$random_hour}:{$random_minute}:00"); } diff --git a/includes/class-rl-mailwarmer-email-helper.php b/includes/class-rl-mailwarmer-email-helper.php deleted file mode 100644 index 0450668..0000000 --- a/includes/class-rl-mailwarmer-email-helper.php +++ /dev/null @@ -1,471 +0,0 @@ - '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) - { - // Get the post object - $post = is_numeric($email_account) ? get_post($email_account) : $email_account; - if (!$post || $post->post_type !== 'email-account') { - 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 - "); - - 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 = [ - '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($post->post_title, $settings); - log_to_file("check_mail_login - IMAP Result for " . $post->post_title . ": ", $imap_result); - $results['IMAP'] = $imap_result ? __('IMAP connection successful.', 'rl-mailwarmer') : __('IMAP connection failed.', 'rl-mailwarmer'); - } - - // Validate SMTP connection if required - if ($protocol === null || strtoupper($protocol) === 'SMTP') { - $smtp_result = self::validate_smtp_connection($post->post_title, $settings); - log_to_file("check_mail_login - SMTP Result for " . $post->post_title . ": ", $imap_result); - $results['SMTP'] = $smtp_result ? __('SMTP connection successful.', 'rl-mailwarmer') : __('SMTP connection failed.', 'rl-mailwarmer'); - } - 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. - */ - private 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 string $email The email address. - * @param array $settings The server settings (imap_server, imap_port, imap_password). - * @return bool True if the connection is successful, false otherwise. - */ - private static function validate_imap_connection($email, $settings) - { - if ( empty($settings['imap_server']) || empty($settings['imap_port']) ) { - return false; // Missing required settings - } - - if (!empty($settings['imap_password'])) { - $password = $settings['imap_password']; - } else { - $password = $settings['mail_password']; - } - - // Try connecting to the IMAP server - $imap_stream = @imap_open( - '{' . $settings['imap_server'] . ':' . $settings['imap_port'] . '/imap/ssl}', - $email, - $password - ); - - if ($imap_stream) { - imap_close($imap_stream); // Close connection if successful - return true; - } - - 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. - */ - private static function validate_smtp_connection($email, $settings) - { - if (empty($settings['smtp_server']) || empty($settings['smtp_port']) ) { - return false; // Missing required settings - } - - if (!empty($settings['smtp_password'])) { - $password = $settings['smtp_password']; - } else { - $password = $settings['mail_password']; - } - - $test_to_email = "ruben@redlotusaustin.com"; - - 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($email) - ->to($test_to_email) - ->subject('SMTP Connection for ' . $email) - ->text('This is a test email to verify SMTP connection for ' . $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) - { - // 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}"; - - // Create the email-account post - // $post_id = RL_MailWarmer_Email_Helper::modify_email_account([ - // 'action' => 'create', - // 'email' => $email_address, - // 'metadata' => [ - // 'full_name' => $full_name, - // 'mail_password' => bin2hex(random_bytes(8)), // Generate a secure random password - // ], - // ]); - - // if (!$post_id) { - // throw new Exception(__('Failed to create email account post.', 'rl-mailwarmer')); - // } - - // Add to results - $generated_accounts[] = [ - 'full_name' => $full_name, - 'email_address' => $email_address, - ]; - } - - return $generated_accounts; - } - - - /** - * 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_id', true); - if (!$domain_id) { - return new WP_Error('missing_domain', __('No associated domain found.', 'rl-mailwarmer')); - } - - $server_ids = get_post_meta($domain_id, 'associated_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); - [$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 = "virtualmin"; - if ($action === 'create') { - $command .= " create-user --domain $domain --user $username --pass $password"; - } elseif ($action === 'update') { - $command .= " modify-user --domain $domain --user $username --pass $password"; - } elseif ($action === 'delete') { - $command .= " delete-user --domain $domain --user $username"; - } else { - return new WP_Error('invalid_action', __('Invalid action specified.', 'rl-mailwarmer')); - } - - // 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 - } - - - /** - * Send or reply to a conversation email. - * - * @param int $conversation_id The conversation post ID. - * @param int $account_id The email account post ID. - * @return bool True on success, false on failure. - */ - public static function send_conversation_mail(int $conversation_id, int $account_id) - { - // Implementation goes here - } -} diff --git a/includes/class-rl-mailwarmer-scheduler.php b/includes/class-rl-mailwarmer-scheduler.php index 50ba711..59efe3e 100644 --- a/includes/class-rl-mailwarmer-scheduler.php +++ b/includes/class-rl-mailwarmer-scheduler.php @@ -1,89 +1,94 @@ 60, // 60 seconds = 1 minute + 'display' => __('Every Minute', 'rl-mailwarmer'), + ]; + $schedules['every_five_minutes'] = [ + 'interval' => 300, + 'display' => __('Every 5 Minutes', 'rl-mailwarmer'), + ]; + } + return $schedules; + } + + /** + * Schedule the cron task if not already scheduled. + */ + public static function schedule_cron_jobs() { + + // Message handler + if (!wp_next_scheduled('rl_mailwarmer_process_messages')) { + wp_schedule_event(time(), 'every_minute', 'rl_mailwarmer_process_messages'); + } + + // Conversation handler + if (!wp_next_scheduled('rl_mailwarmer_process_upcoming_conversations')) { + wp_schedule_event(time(), 'every_minute', 'rl_mailwarmer_process_upcoming_conversations'); } } /** - * Clear WP-Cron jobs on deactivation. + * Clear the scheduled task. */ - public static function clear_cron_jobs() - { - $timestamp = wp_next_scheduled('rl_mailwarmer_send_emails'); + public static function clear_cron_jobs() { + + // Message handler + $timestamp = wp_next_scheduled('rl_mailwarmer_process_messages'); if ($timestamp) { - wp_unschedule_event($timestamp, 'rl_mailwarmer_send_emails'); + wp_unschedule_event($timestamp, 'rl_mailwarmer_process_messages'); + } + + // Conversation handler + $timestamp = wp_next_scheduled('rl_mailwarmer_process_upcoming_conversations'); + if ($timestamp) { + wp_unschedule_event($timestamp, 'rl_mailwarmer_process_upcoming_conversations'); } } /** - * Send scheduled emails for campaigns. + * Process pending messages by delegating to the Message Handler. */ - public static function send_scheduled_emails() - { - // Fetch campaigns with active schedules - $campaigns = get_posts([ - 'post_type' => 'campaign', - 'post_status' => 'publish', - 'meta_query' => [ - [ - 'key' => 'email_schedule', - 'compare' => 'EXISTS', - ], - ], - ]); + public static function process_pending_messages() { + // log_to_file("schedule_cron_jobs ====================== Running Cron to process messages ========================"); + // RL_MailWarmer_Message_Handler::process_pending_messages(); + } - if (!$campaigns) { - return; - } - - foreach ($campaigns as $campaign) { - // Get scheduled emails - $schedule = get_post_meta($campaign->ID, 'email_schedule', true); - - if (!is_array($schedule)) { - continue; - } - - foreach ($schedule as $email) { - // Check if the email is ready to be sent - $send_time = strtotime($email['send_time']); - if ($send_time > time()) { - continue; - } - - // Send the email - RL_MailWarmer_Email_Handler::send_email($email); - - // Mark as sent - $email['sent'] = true; - } - - // Update the schedule - update_post_meta($campaign->ID, 'email_schedule', $schedule); - } + /** + * 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(); } } diff --git a/includes/composer.json b/includes/composer.json deleted file mode 100644 index e22e372..0000000 --- a/includes/composer.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "require": { - "guzzlehttp/guzzle": "^7.9", - "symfony/mailer": "^7.2", - "symfony/http-client": "^7.2", - "phpseclib/phpseclib": "^3.0" - } -} diff --git a/includes/composer.lock b/includes/composer.lock deleted file mode 100644 index d0608e0..0000000 --- a/includes/composer.lock +++ /dev/null @@ -1,2007 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "66d40a08be9462f6349bfc464e6ea505", - "packages": [ - { - "name": "doctrine/deprecations", - "version": "1.1.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" - }, - "time": "2024-01-30T19:34:25+00:00" - }, - { - "name": "doctrine/lexer", - "version": "2.1.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9 || ^12", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.21" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Lexer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": [ - "annotations", - "docblock", - "lexer", - "parser", - "php" - ], - "support": { - "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", - "type": "tidelift" - } - ], - "time": "2024-02-05T11:35:39+00:00" - }, - { - "name": "egulias/email-validator", - "version": "3.2.6", - "source": { - "type": "git", - "url": "https://github.com/egulias/EmailValidator.git", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^1.2|^2", - "php": ">=7.2", - "symfony/polyfill-intl-idn": "^1.15" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.8|^9.3.3", - "vimeo/psalm": "^4" - }, - "suggest": { - "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Egulias\\EmailValidator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Eduardo Gulias Davis" - } - ], - "description": "A library for validating emails against several RFCs", - "homepage": "https://github.com/egulias/EmailValidator", - "keywords": [ - "email", - "emailvalidation", - "emailvalidator", - "validation", - "validator" - ], - "support": { - "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.6" - }, - "funding": [ - { - "url": "https://github.com/egulias", - "type": "github" - } - ], - "time": "2023-06-01T07:04:22+00:00" - }, - { - "name": "guzzlehttp/guzzle", - "version": "7.9.2", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.3", - "guzzlehttp/psr7": "^2.7.0", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", - "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "GuzzleHttp\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2024-07-24T11:22:20+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2024-10-17T10:06:22+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "2.7.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", - "phpunit/phpunit": "^8.5.39 || ^9.6.20" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2024-07-18T11:15:46+00:00" - }, - { - "name": "paragonie/constant_time_encoding", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", - "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", - "shasum": "" - }, - "require": { - "php": "^8" - }, - "require-dev": { - "phpunit/phpunit": "^9", - "vimeo/psalm": "^4|^5" - }, - "type": "library", - "autoload": { - "psr-4": { - "ParagonIE\\ConstantTime\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com", - "role": "Maintainer" - }, - { - "name": "Steve 'Sc00bz' Thomas", - "email": "steve@tobtu.com", - "homepage": "https://www.tobtu.com", - "role": "Original Developer" - } - ], - "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", - "keywords": [ - "base16", - "base32", - "base32_decode", - "base32_encode", - "base64", - "base64_decode", - "base64_encode", - "bin2hex", - "encoding", - "hex", - "hex2bin", - "rfc4648" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/constant_time_encoding/issues", - "source": "https://github.com/paragonie/constant_time_encoding" - }, - "time": "2024-05-08T12:36:18+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "time": "2020-10-15T08:29:30+00:00" - }, - { - "name": "phpseclib/phpseclib", - "version": "3.0.42", - "source": { - "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98", - "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98", - "shasum": "" - }, - "require": { - "paragonie/constant_time_encoding": "^1|^2|^3", - "paragonie/random_compat": "^1.4|^2.0|^9.99.99", - "php": ">=5.6.1" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "suggest": { - "ext-dom": "Install the DOM extension to load XML formatted public keys.", - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." - }, - "type": "library", - "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], - "psr-4": { - "phpseclib3\\": "phpseclib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } - ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", - "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" - ], - "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42" - }, - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], - "time": "2024-09-16T03:06:04+00:00" - }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, - { - "name": "psr/event-dispatcher", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/event-dispatcher.git", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", - "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", - "shasum": "" - }, - "require": { - "php": ">=7.2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\EventDispatcher\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Standard interfaces for event handling.", - "keywords": [ - "events", - "psr", - "psr-14" - ], - "support": { - "issues": "https://github.com/php-fig/event-dispatcher/issues", - "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" - }, - "time": "2019-01-08T18:20:26+00:00" - }, - { - "name": "psr/http-client", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", - "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client" - }, - "time": "2023-09-23T14:17:50+00:00" - }, - { - "name": "psr/http-factory", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", - "shasum": "" - }, - "require": { - "php": ">=7.1", - "psr/http-message": "^1.0 || ^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory" - }, - "time": "2024-04-15T12:06:14+00:00" - }, - { - "name": "psr/http-message", - "version": "2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" - }, - "time": "2023-04-04T09:54:51+00:00" - }, - { - "name": "psr/log", - "version": "3.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" - }, - "time": "2024-09-11T13:17:53+00:00" - }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/event-dispatcher", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/event-dispatcher-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/service-contracts": "<2.5" - }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0|3.0" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, - { - "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/event-dispatcher": "^1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - }, - { - "name": "symfony/http-client", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client.git", - "reference": "955e43336aff03df1e8a8e17daefabb0127a313b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/955e43336aff03df1e8a8e17daefabb0127a313b", - "reference": "955e43336aff03df1e8a8e17daefabb0127a313b", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "~3.4.3|^3.5.1", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "amphp/amp": "<2.5", - "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" - }, - "provide": { - "php-http/async-client-implementation": "*", - "php-http/client-implementation": "*", - "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "3.0" - }, - "require-dev": { - "amphp/http-client": "^4.2.1|^5.0", - "amphp/http-tunnel": "^1.0|^2.0", - "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4|^2.0", - "nyholm/psr7": "^1.0", - "php-http/httplug": "^1.0|^2.0", - "psr/http-client": "^1.0", - "symfony/amphp-http-client-meta": "^1.0|^2.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/rate-limiter": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", - "homepage": "https://symfony.com", - "keywords": [ - "http" - ], - "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-29T08:22:02+00:00" - }, - { - "name": "symfony/http-client-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", - "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\HttpClient\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to HTTP clients", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-25T12:02:18+00:00" - }, - { - "name": "symfony/mailer", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/mailer.git", - "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/e4d358702fb66e4c8a2af08e90e7271a62de39cc", - "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc", - "shasum": "" - }, - "require": { - "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.2", - "psr/event-dispatcher": "^1", - "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/mime": "^7.2", - "symfony/service-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/messenger": "<6.4", - "symfony/mime": "<6.4", - "symfony/twig-bridge": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mailer\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Helps sending emails", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/mailer/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-25T15:21:05+00:00" - }, - { - "name": "symfony/mime", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/cc84a4b81f62158c3846ac7ff10f696aae2b524d", - "reference": "cc84a4b81f62158c3846ac7ff10f696aae2b524d", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "conflict": { - "egulias/email-validator": "~3.0.0", - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<6.4", - "symfony/serializer": "<6.4.3|>7.0,<7.0.3" - }, - "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1|^4", - "league/html-to-markdown": "^5.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/serializer": "^6.4.3|^7.0.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Mime\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows manipulating MIME messages", - "homepage": "https://symfony.com", - "keywords": [ - "mime", - "mime-type" - ], - "support": { - "source": "https://github.com/symfony/mime/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-23T09:19:39+00:00" - }, - { - "name": "symfony/polyfill-intl-idn", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", - "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", - "shasum": "" - }, - "require": { - "php": ">=7.2", - "symfony/polyfill-intl-normalizer": "^1.10" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" - }, - { - "name": "Trevor Rowbotham", - "email": "trevor.rowbotham@pm.me" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "idn", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.31.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", - "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, - { - "name": "symfony/service-contracts", - "version": "v3.5.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - }, - "exclude-from-classmap": [ - "/Test/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:20:29+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": {}, - "prefer-stable": false, - "prefer-lowest": false, - "platform": {}, - "platform-dev": {}, - "plugin-api-version": "2.6.0" -} diff --git a/includes/rl-mailwarmer-ajax.php b/includes/rl-mailwarmer-ajax.php index ccd435c..04dd5ea 100644 --- a/includes/rl-mailwarmer-ajax.php +++ b/includes/rl-mailwarmer-ajax.php @@ -32,12 +32,52 @@ add_action('wp_ajax_rl_mailwarmer_check_domain_health', function () { } }); +add_action('wp_ajax_rl_mailwarmer_update_mx_record', function () { + // Verify nonce + if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'update_mx_record_nonce')) { + wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); + } + + // log_to_file("wp_ajax_rl_mailwarmer_update_mx_record - Running"); + + // Get input values + $post_id = intval($_POST['post_id']); + $action = sanitize_text_field($_POST['action_type']); + $content = sanitize_text_field($_POST['content']); + $priority = intval(sanitize_text_field($_POST['priority'])); + $ttl = intval($_POST['ttl']); + + if (!$post_id || !$content || !$action) { + wp_send_json_error(__('Missing required fields', 'rl-mailwarmer')); + } + $domain = RL_MailWarmer_Domain_Helper::get_domain_post($post_id); + + // log_to_file("wp_ajax_rl_mailwarmer_update_mx_record - Post ID: $post_id $content $action $priority $ttl"); + + // Call the update_mx_record function + try { + log_to_file("wp_ajax_rl_mailwarmer_update_mx_record - Before"); + $result = RL_MailWarmer_Domain_Helper::update_mx_record($domain, $content, $priority, $ttl, $action); + log_to_file("wp_ajax_rl_mailwarmer_update_mx_record - After"); + + if ($result) { + wp_send_json_success(__('MX record updated successfully.', 'rl-mailwarmer')); + } else { + wp_send_json_error(__('Failed to update MX record.', 'rl-mailwarmer')); + } + } catch (Exception $e) { + wp_send_json_error($e->getMessage()); + } +}); + add_action('wp_ajax_rl_mailwarmer_update_spf_record', function () { // Verify nonce if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'update_spf_record_nonce')) { wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); } + // log_to_file("wp_ajax_rl_mailwarmer_update_spf_record - Running"); + // Get input values $post_id = intval($_POST['post_id']); $host = sanitize_text_field($_POST['host']); @@ -48,10 +88,15 @@ add_action('wp_ajax_rl_mailwarmer_update_spf_record', function () { if (!$post_id || !$host || !$action) { wp_send_json_error(__('Missing required fields', 'rl-mailwarmer')); } + $domain = RL_MailWarmer_Domain_Helper::get_domain_post($post_id); + + // log_to_file("wp_ajax_rl_mailwarmer_update_spf_record - Post ID: $post_id $host $action $all_policy $ttl"); // Call the update_spf_record function try { - $result = RL_MailWarmer_Domain_Helper::update_spf_record($post_id, $host, $action, $all_policy, $ttl); + // log_to_file("wp_ajax_rl_mailwarmer_update_spf_record - Before"); + $result = RL_MailWarmer_Domain_Helper::update_spf_record($domain, $host, $action, $all_policy, $ttl); + // log_to_file("wp_ajax_rl_mailwarmer_update_spf_record - After"); if ($result) { wp_send_json_success(__('SPF record updated successfully.', 'rl-mailwarmer')); @@ -164,10 +209,10 @@ add_action('wp_ajax_rl_mailwarmer_create_dns_backup', function () { if (!$post_id) { wp_send_json_error(__('Invalid post ID', 'rl-mailwarmer')); } - + $domain = get_post($post_id); // Call the create_dns_backup function try { - $backup_id = RL_MailWarmer_Domain_Helper::create_dns_backup($post_id); + $backup_id = RL_MailWarmer_Domain_Helper::export_dns_zone($domain); if (is_wp_error($backup_id)) { wp_send_json_error($backup_id->get_error_message()); } diff --git a/includes/rl-mailwarmer-domain-admin.php b/includes/rl-mailwarmer-domain-admin.php index 692454b..e2c6430 100644 --- a/includes/rl-mailwarmer-domain-admin.php +++ b/includes/rl-mailwarmer-domain-admin.php @@ -3,14 +3,15 @@ add_action('admin_enqueue_scripts', function ($hook) { if ($hook === 'post.php' || $hook === 'post-new.php') { global $post; + + wp_enqueue_style( + 'rl-mailwarmer-admin-css', + RL_MAILWARMER_URL . '/css/admin-style.css', // Path to your CSS file + [], + '1.0.3' // Version number + ); if ($post->post_type === 'domain') { - wp_enqueue_style( - 'rl-mailwarmer-admin-css', - RL_MAILWARMER_URL . '/css/admin-style.css', // Path to your CSS file - [], - '1.0.0' // Version number - ); wp_enqueue_script('rl-mailwarmer-admin-script', RL_MAILWARMER_URL . '/js/admin-check-domain-health.js', ['jquery'], null, true); wp_localize_script('rl-mailwarmer-admin-script', 'rlMailWarmer', [ 'ajax_url' => admin_url('admin-ajax.php'), @@ -23,6 +24,12 @@ add_action('admin_enqueue_scripts', function ($hook) { 'nonce' => wp_create_nonce('update_spf_record_nonce'), 'post_id' => $post->ID ]); + wp_enqueue_script('rl-mailwarmer-mx-script', RL_MAILWARMER_URL . '/js/admin-update-mx.js', ['jquery'], null, true); + wp_localize_script('rl-mailwarmer-mx-script', 'rlMailWarmerMx', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('update_mx_record_nonce'), + 'post_id' => $post->ID + ]); wp_enqueue_script('rl-mailwarmer-dmarc-script', RL_MAILWARMER_URL . '/js/admin-update-dmarc.js', ['jquery'], null, true); wp_localize_script('rl-mailwarmer-dmarc-script', 'rlMailWarmerDmarc', [ 'ajax_url' => admin_url('admin-ajax.php'), @@ -134,6 +141,14 @@ add_action('add_meta_boxes', function () { 'side', // Context 'default' // Priority ); + add_meta_box( + 'update_mx_record_box', // Meta box ID + __('Update MX Record', 'rl-mailwarmer'), // Title + 'rl_mailwarmer_render_update_mx_record_box', // Callback function + 'domain', // Post type + 'side', // Context + 'default' // Priority + ); add_meta_box( 'update_spf_record_box', // Meta box ID __('Update SPF Record', 'rl-mailwarmer'), // Title @@ -223,6 +238,51 @@ function rl_mailwarmer_render_fix_deliverability_dns_issues_box($post) +/** + * Render the fields for the "Update MX Record" meta box. + * + * @param WP_Post $post The current post object. + */ +function rl_mailwarmer_render_update_mx_record_box($post) +{ + // Add a nonce field for security + wp_nonce_field('update_mx_record_nonce', 'update_mx_record_nonce_field'); + + // Render the fields + ?> +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + __('Age', 'rl-mailwarmer'), 'domain_days_to_expiration' => __('Days to Expiration', 'rl-mailwarmer'), 'a_record_valid' => __('A Record Valid', 'rl-mailwarmer'), - 'a_record_resolves' => __('A Record Resolves To', 'rl-mailwarmer'), + // 'a_record_resolves' => __('A Record Resolves To', 'rl-mailwarmer'), 'http_status' => __('HTTP Status', 'rl-mailwarmer'), 'https_enabled' => __('HTTPS Enabled', 'rl-mailwarmer'), - 'mx_record_valid' => __('MX Valid', 'rl-mailwarmer'), - 'mx_record_ptr_valid' => __('PTR Valid', 'rl-mailwarmer'), - 'mx_record_ptr_match' => __('PTR Matches', 'rl-mailwarmer'), - 'spf_record_exists' => __('SPF Exists', 'rl-mailwarmer'), - 'spf_record_content' => __('SPF Valid', 'rl-mailwarmer'), - 'spf_record_ttl' => __('SPF TTL', 'rl-mailwarmer'), - 'spf_record_all_mechanism' => __('SPF All Mechanism', 'rl-mailwarmer'), + 'mx_record_valid' => __('Valid MX Record', 'rl-mailwarmer'), + // 'mx_record_ptr_valid' => __('PTR Valid', 'rl-mailwarmer'), + // 'mx_record_ptr_match' => __('PTR Matches', 'rl-mailwarmer'), + // 'spf_record_exists' => __('SPF Exists', 'rl-mailwarmer'), + 'spf_record_content' => __('SPF Record', 'rl-mailwarmer'), + // 'spf_record_ttl' => __('SPF TTL', 'rl-mailwarmer'), + // 'spf_record_all_mechanism' => __('SPF All Mechanism', 'rl-mailwarmer'), 'dmarc_record_exists' => __('DMARC Exists', 'rl-mailwarmer'), 'dmarc_policy' => __('DMARC Policy', 'rl-mailwarmer'), - 'dmarc_sp_policy' => __('DMARC SP Policy', 'rl-mailwarmer'), - 'dmarc_percentage' => __('DMARC Percentage', 'rl-mailwarmer'), - 'dmarc_aspf' => __('DMARC ASPF', 'rl-mailwarmer'), - 'dmarc_adkim' => __('DMARC ADKIM', 'rl-mailwarmer'), - 'dmarc_aggregate_rpt' => __('DMARC Aggregate RPT', 'rl-mailwarmer'), - 'dmarc_forensic_rpt' => __('DMARC Forensic RPT', 'rl-mailwarmer'), - 'dmarc_report_format' => __('DMARC Report Format', 'rl-mailwarmer'), - 'dmarc_report_interval' => __('DMARC Report Interval', 'rl-mailwarmer'), + // 'dmarc_sp_policy' => __('DMARC SP Policy', 'rl-mailwarmer'), + // 'dmarc_percentage' => __('DMARC Percentage', 'rl-mailwarmer'), + // 'dmarc_aspf' => __('DMARC ASPF', 'rl-mailwarmer'), + // 'dmarc_adkim' => __('DMARC ADKIM', 'rl-mailwarmer'), + // 'dmarc_aggregate_rpt' => __('DMARC Aggregate RPT', 'rl-mailwarmer'), + // 'dmarc_forensic_rpt' => __('DMARC Forensic RPT', 'rl-mailwarmer'), + // 'dmarc_report_format' => __('DMARC Report Format', 'rl-mailwarmer'), + // 'dmarc_report_interval' => __('DMARC Report Interval', 'rl-mailwarmer'), 'dkim_records' => __('DKIM Records', 'rl-mailwarmer'), ]; @@ -555,6 +615,7 @@ add_filter('manage_edit-domain-health-report_sortable_columns', function ($colum $columns['domain_name'] = 'domain_name'; $columns['domain_valid'] = 'domain_valid'; $columns['domain_age'] = 'domain_age'; + $columns['mx_record_valid'] = 'mx_record_valid'; // Add more sortable columns as needed return $columns; }); @@ -572,30 +633,31 @@ add_filter('manage_domain_posts_columns', function ($columns) { // Add custom columns $custom_columns = [ + // 'domain_name' => __('Domain Name', 'rl-mailwarmer'), 'domain_valid' => __('Valid', 'rl-mailwarmer'), 'domain_age' => __('Age', 'rl-mailwarmer'), 'domain_days_to_expiration' => __('Days to Expiration', 'rl-mailwarmer'), 'a_record_valid' => __('A Record Valid', 'rl-mailwarmer'), - 'a_record_resolves' => __('A Record Resolves To', 'rl-mailwarmer'), + // 'a_record_resolves' => __('A Record Resolves To', 'rl-mailwarmer'), 'http_status' => __('HTTP Status', 'rl-mailwarmer'), 'https_enabled' => __('HTTPS Enabled', 'rl-mailwarmer'), - 'mx_record_valid' => __('MX Valid', 'rl-mailwarmer'), - 'mx_record_ptr_valid' => __('PTR Valid', 'rl-mailwarmer'), - 'mx_record_ptr_match' => __('PTR Matches', 'rl-mailwarmer'), - 'spf_record_exists' => __('SPF Exists', 'rl-mailwarmer'), - 'spf_record_is_valid' => __('SPF Valid', 'rl-mailwarmer'), - 'spf_record_ttl' => __('SPF TTL', 'rl-mailwarmer'), - 'spf_record_all_mechanism' => __('SPF All Mechanism', 'rl-mailwarmer'), + 'mx_record' => __('MX Record', 'rl-mailwarmer'), + // 'mx_record_ptr_valid' => __('PTR Valid', 'rl-mailwarmer'), + // 'mx_record_ptr_match' => __('PTR Matches', 'rl-mailwarmer'), + // 'spf_record_exists' => __('SPF Exists', 'rl-mailwarmer'), + 'spf_record_content' => __('SPF Record', 'rl-mailwarmer'), + // 'spf_record_ttl' => __('SPF TTL', 'rl-mailwarmer'), + // 'spf_record_all_mechanism' => __('SPF All Mechanism', 'rl-mailwarmer'), 'dmarc_record_exists' => __('DMARC Exists', 'rl-mailwarmer'), 'dmarc_policy' => __('DMARC Policy', 'rl-mailwarmer'), - 'dmarc_sp_policy' => __('DMARC SP Policy', 'rl-mailwarmer'), - 'dmarc_percentage' => __('DMARC Percentage', 'rl-mailwarmer'), - 'dmarc_aspf' => __('DMARC ASPF', 'rl-mailwarmer'), - 'dmarc_adkim' => __('DMARC ADKIM', 'rl-mailwarmer'), - 'dmarc_aggregate_rpt' => __('DMARC Aggregate RPT', 'rl-mailwarmer'), - 'dmarc_forensic_rpt' => __('DMARC Forensic RPT', 'rl-mailwarmer'), - 'dmarc_report_format' => __('DMARC Report Format', 'rl-mailwarmer'), - 'dmarc_report_interval' => __('DMARC Report Interval', 'rl-mailwarmer'), + // 'dmarc_sp_policy' => __('DMARC SP Policy', 'rl-mailwarmer'), + // 'dmarc_percentage' => __('DMARC Percentage', 'rl-mailwarmer'), + // 'dmarc_aspf' => __('DMARC ASPF', 'rl-mailwarmer'), + // 'dmarc_adkim' => __('DMARC ADKIM', 'rl-mailwarmer'), + // 'dmarc_aggregate_rpt' => __('DMARC Aggregate RPT', 'rl-mailwarmer'), + // 'dmarc_forensic_rpt' => __('DMARC Forensic RPT', 'rl-mailwarmer'), + // 'dmarc_report_format' => __('DMARC Report Format', 'rl-mailwarmer'), + // 'dmarc_report_interval' => __('DMARC Report Interval', 'rl-mailwarmer'), 'dkim_records' => __('DKIM Records', 'rl-mailwarmer'), ]; @@ -610,108 +672,151 @@ add_filter('manage_domain_posts_columns', function ($columns) { */ add_action('manage_domain_posts_custom_column', function ($column, $post_id) { $meta = get_post_meta($post_id); + $json_report = $meta['domain_health_report'][0]; + + // Decode the JSON into an associative array + $report = json_decode($json_report, true); + // var_dump($report); + + // // Check for JSON decoding errors + // if (json_last_error() !== JSON_ERROR_NONE) { + // log_to_file("rl_mailwarmer_render_domain_metadata_table - JSON decode error for post {$post->ID}: " . json_last_error_msg()); + // // return null; + // } + // log_to_file("manage_domain_posts_custom_column - Report array: ", $report["domain_health"]); + + // Assign metadata to the array using $post_meta + $metadata = [ + 'domain_valid' => $report["domain_health"]['registration_valid'] ?? '', + 'domain_age' => $report["domain_health"]['domain_age'] ?? '', + 'domain_days_to_expiration' => $report["domain_health"]['days_to_expiration'] ?? '', + 'a_record_ip' => $report["a_record"]['ip'] ?? '', + 'http_status' => $report["a_record"]['http_status'] ?? '', + 'https_enabled' => $report["a_record"]['https_enabled'] ?? '', + 'mx_record_host' => $report["mx_record"]['host'] ?? '', + 'mx_record_ptr_valid' => $report["mx_record"]['ptr_valid'] ?? '', + 'mx_record_ptr_match' => $report["mx_record"]['ptr_matches'] ?? '', + 'spf_record' => $report["spf_record"]['content'] ?? '', + 'spf_record_ttl' => $report["spf_record"]['ttl'] ?? '', + 'spf_record_all_mechanism' => $report["spf_record"]['all_mechanism'] ?? '', + 'dmarc_record_exists' => $report["dmarc_record"]['exists'] ?? '', + 'dmarc_record_content' => $report["dmarc_record"]['content'] ?? '', + 'dmarc_record_ttl' => $report["dmarc_record"]['ttl'] ?? '', + 'dmarc_policy' => $report["dmarc_record"]['policy'] ?? '', + 'dmarc_sp_policy' => $report["dmarc_record"]['sp_policy'] ?? '', + 'dmarc_percentage' => $report["dmarc_record"]['percentage'] ?? '', + 'dmarc_aspf' => $report["dmarc_record"]['aspf'] ?? '', + 'dmarc_adkim' => $report["dmarc_record"]['adkim'] ?? '', + 'dmarc_aggregate_rpt' => $report["dmarc_record"]['aggregate_rpt'] ?? '', + 'dmarc_forensic_rpt' => $report["dmarc_record"]['forensic_rpt'] ?? '', + 'dmarc_report_format' => $report["dmarc_record"]['report_format'] ?? '', + 'dmarc_report_interval' => $report["dmarc_record"]['report_interval'] ?? '', + 'dkim_records' => $report['dkim_records'] ?? '', + ]; + + // log_to_file("manage_domain_posts_custom_column - Health report: ", $domain_report); switch ($column) { case 'domain_valid': - echo !empty($meta['domain_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + echo !empty($report["domain_health"]['registration_valid']) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); break; case 'domain_age': - echo esc_html($meta['domain_age'][0] ?? ''); + echo !empty($report["domain_health"]['domain_age']) ? $report["domain_health"]['domain_age'] : __('0', 'rl-mailwarmer'); break; - case 'domain_days_to_expiration': - echo esc_html($meta['domain_days_to_expiration'][0] ?? ''); + case 'mx_record': + echo !empty($report["mx_record"]['host']) ? $report["mx_record"]['host'] : 'Unset'; break; - case 'a_record_valid': - echo !empty($meta['a_record_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'domain_days_to_expiration': + // echo esc_html($meta['domain_days_to_expiration'][0] ?? ''); + // break; - case 'a_record_resolves': - echo esc_html($meta['a_record_resolves'][0] ?? ''); - break; + // case 'a_record_valid': + // echo !empty($meta['a_record_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'http_status': - echo esc_html($meta['http_status'][0] ?? ''); - break; + // case 'a_record_resolves': + // echo esc_html($meta['a_record_resolves'][0] ?? ''); + // break; - case 'https_enabled': - echo !empty($meta['https_enabled'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'http_status': + // echo esc_html($meta['http_status'][0] ?? ''); + // break; - case 'mx_record_valid': - echo !empty($meta['mx_record_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'https_enabled': + // echo !empty($meta['https_enabled'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'mx_record_ptr_valid': - echo !empty($meta['mx_record_ptr_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'mx_record_ptr_valid': + // echo !empty($meta['mx_record_ptr_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'mx_record_ptr_match': - echo !empty($meta['mx_record_ptr_match'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'mx_record_ptr_match': + // echo !empty($meta['mx_record_ptr_match'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'spf_record_exists': - echo !empty($meta['spf_record_exists'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'spf_record_exists': + // echo !empty($meta['spf_record_exists'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'spf_record_is_valid': - echo !empty($meta['spf_record_is_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'spf_record_is_valid': + // echo !empty($meta['spf_record_is_valid'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'spf_record_ttl': - echo esc_html($meta['spf_record_ttl'][0] ?? ''); - break; + // case 'spf_record_ttl': + // echo esc_html($meta['spf_record_ttl'][0] ?? ''); + // break; - case 'spf_record_all_mechanism': - echo esc_html($meta['spf_record_all_mechanism'][0] ?? ''); - break; + // case 'spf_record_all_mechanism': + // echo esc_html($meta['spf_record_all_mechanism'][0] ?? ''); + // break; - case 'dmarc_record_exists': - echo !empty($meta['dmarc_record_exists'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); - break; + // case 'dmarc_record_exists': + // echo !empty($meta['dmarc_record_exists'][0]) ? __('Yes', 'rl-mailwarmer') : __('No', 'rl-mailwarmer'); + // break; - case 'dmarc_policy': - echo esc_html($meta['dmarc_policy'][0] ?? ''); - break; + // case 'dmarc_policy': + // echo esc_html($meta['dmarc_policy'][0] ?? ''); + // break; - case 'dmarc_sp_policy': - echo esc_html($meta['dmarc_sp_policy'][0] ?? ''); - break; + // case 'dmarc_sp_policy': + // echo esc_html($meta['dmarc_sp_policy'][0] ?? ''); + // break; - case 'dmarc_percentage': - echo esc_html($meta['dmarc_percentage'][0] ?? ''); - break; + // case 'dmarc_percentage': + // echo esc_html($meta['dmarc_percentage'][0] ?? ''); + // break; - case 'dmarc_aspf': - echo esc_html($meta['dmarc_aspf'][0] ?? ''); - break; + // case 'dmarc_aspf': + // echo esc_html($meta['dmarc_aspf'][0] ?? ''); + // break; - case 'dmarc_adkim': - echo esc_html($meta['dmarc_adkim'][0] ?? ''); - break; + // case 'dmarc_adkim': + // echo esc_html($meta['dmarc_adkim'][0] ?? ''); + // break; - case 'dmarc_aggregate_rpt': - echo esc_html($meta['dmarc_aggregate_rpt'][0] ?? ''); - break; + // case 'dmarc_aggregate_rpt': + // echo esc_html($meta['dmarc_aggregate_rpt'][0] ?? ''); + // break; - case 'dmarc_forensic_rpt': - echo esc_html($meta['dmarc_forensic_rpt'][0] ?? ''); - break; + // case 'dmarc_forensic_rpt': + // echo esc_html($meta['dmarc_forensic_rpt'][0] ?? ''); + // break; - case 'dmarc_report_format': - echo esc_html($meta['dmarc_report_format'][0] ?? ''); - break; + // case 'dmarc_report_format': + // echo esc_html($meta['dmarc_report_format'][0] ?? ''); + // break; - case 'dmarc_report_interval': - echo esc_html($meta['dmarc_report_interval'][0] ?? ''); - break; + // case 'dmarc_report_interval': + // echo esc_html($meta['dmarc_report_interval'][0] ?? ''); + // break; - case 'dkim_records': - echo esc_html($meta['dkim_records'][0] ?? ''); - break; + // case 'dkim_records': + // echo esc_html($meta['dkim_records'][0] ?? ''); + // break; default: echo ''; @@ -723,6 +828,7 @@ add_filter('manage_edit-domain_sortable_columns', function ($columns) { // $columns['domain_name'] = 'domain_name'; $columns['domain_valid'] = 'domain_valid'; $columns['domain_age'] = 'domain_age'; + $columns['mx_record'] = 'mx_record'; // Add more sortable columns as needed return $columns; }); @@ -748,35 +854,52 @@ add_action('add_meta_boxes', function () { */ function rl_mailwarmer_render_domain_metadata_table($post) { - // Fetch all metadata for the current post - $post_meta = get_post_meta($post->ID); + + // Fetch the domain health report from post meta + $json_report = get_post_meta($post->ID, 'domain_health_report', true); + + // Check if the meta field exists and is not empty + if (empty($json_report)) { + return null; // or throw an exception, depending on your error handling preference + } + + // Decode the JSON into an associative array + $report = json_decode($json_report, true); + + // Check for JSON decoding errors + if (json_last_error() !== JSON_ERROR_NONE) { + log_to_file("rl_mailwarmer_render_domain_metadata_table - JSON decode error for post {$post->ID}: " . json_last_error_msg()); + // return null; + } + // log_to_file("rl_mailwarmer_render_domain_metadata_table - Report array: ", $report); // Assign metadata to the array using $post_meta $metadata = [ - 'domain_valid' => $post_meta['domain_valid'][0] ?? '', - 'domain_age' => $post_meta['domain_age'][0] ?? '', - 'domain_days_to_expiration' => $post_meta['domain_days_to_expiration'][0] ?? '', - 'a_record_resolves' => $post_meta['a_record_resolves'][0] ?? '', - 'http_status' => $post_meta['http_status'][0] ?? '', - 'https_enabled' => $post_meta['https_enabled'][0] ?? '', - 'mx_record_valid' => $post_meta['mx_record_valid'][0] ?? '', - 'mx_record_ptr_valid' => $post_meta['mx_record_ptr_valid'][0] ?? '', - 'mx_record_ptr_match' => $post_meta['mx_record_ptr_match'][0] ?? '', - 'spf_record_exists' => $post_meta['spf_record_exists'][0] ?? '', - 'spf_record_is_valid' => $post_meta['spf_record_is_valid'][0] ?? '', - 'spf_record_ttl' => $post_meta['spf_record_ttl'][0] ?? '', - 'spf_record_all_mechanism' => $post_meta['spf_record_all_mechanism'][0] ?? '', - 'dmarc_record_exists' => $post_meta['dmarc_record_exists'][0] ?? '', - 'dmarc_policy' => $post_meta['dmarc_policy'][0] ?? '', - 'dmarc_sp_policy' => $post_meta['dmarc_sp_policy'][0] ?? '', - 'dmarc_percentage' => $post_meta['dmarc_percentage'][0] ?? '', - 'dmarc_aspf' => $post_meta['dmarc_aspf'][0] ?? '', - 'dmarc_adkim' => $post_meta['dmarc_adkim'][0] ?? '', - 'dmarc_aggregate_rpt' => $post_meta['dmarc_aggregate_rpt'][0] ?? '', - 'dmarc_forensic_rpt' => $post_meta['dmarc_forensic_rpt'][0] ?? '', - 'dmarc_report_format' => $post_meta['dmarc_report_format'][0] ?? '', - 'dmarc_report_interval' => $post_meta['dmarc_report_interval'][0] ?? '', - 'dkim_records' => $post_meta['dkim_records'][0] ?? '', + 'domain_valid' => $report["domain_health"]['registration_valid'] ?? '', + 'domain_age' => $report["domain_health"]['domain_age'] ?? '', + 'domain_days_to_expiration' => $report["domain_health"]['days_to_expiration'] ?? '', + 'a_record_ip' => $report["a_record"]['ip'] ?? '', + 'http_status' => $report["a_record"]['http_status'] ?? '', + 'https_enabled' => $report["a_record"]['https_enabled'] ?? '', + 'mx_record_host' => $report["mx_record"]['host'] ?? '', + 'mx_record_ptr_valid' => $report["mx_record"]['ptr_valid'] ?? '', + 'mx_record_ptr_match' => $report["mx_record"]['ptr_matches'] ?? '', + 'spf_record' => $report["spf_record"]['content'] ?? '', + 'spf_record_ttl' => $report["spf_record"]['ttl'] ?? '', + 'spf_record_all_mechanism' => $report["spf_record"]['all_mechanism'] ?? '', + 'dmarc_record_exists' => $report["dmarc_record"]['exists'] ?? '', + 'dmarc_record_content' => $report["dmarc_record"]['content'] ?? '', + 'dmarc_record_ttl' => $report["dmarc_record"]['ttl'] ?? '', + 'dmarc_policy' => $report["dmarc_record"]['policy'] ?? '', + 'dmarc_sp_policy' => $report["dmarc_record"]['sp_policy'] ?? '', + 'dmarc_percentage' => $report["dmarc_record"]['percentage'] ?? '', + 'dmarc_aspf' => $report["dmarc_record"]['aspf'] ?? '', + 'dmarc_adkim' => $report["dmarc_record"]['adkim'] ?? '', + 'dmarc_aggregate_rpt' => $report["dmarc_record"]['aggregate_rpt'] ?? '', + 'dmarc_forensic_rpt' => $report["dmarc_record"]['forensic_rpt'] ?? '', + 'dmarc_report_format' => $report["dmarc_record"]['report_format'] ?? '', + 'dmarc_report_interval' => $report["dmarc_record"]['report_interval'] ?? '', + 'dkim_records' => $report['dkim_records'] ?? '', ]; // Render the table diff --git a/includes/rl-mailwarmer-functions.php b/includes/rl-mailwarmer-functions.php index 516b235..44a0c7b 100644 --- a/includes/rl-mailwarmer-functions.php +++ b/includes/rl-mailwarmer-functions.php @@ -1,22 +1,28 @@ 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); - $message = $message . " " . $data; + $message = $message . "\r\n" . $data; } else if ($data) { $message = $message . " " . $data; } @@ -25,15 +31,227 @@ function log_to_file($message = false, $data = false){ } } +function rl_mailwarmer_enqueue_scripts() { + wp_enqueue_script('jquery'); + wp_enqueue_script('jquery-ui-core'); + wp_enqueue_script('jquery-ui-tabs'); + wp_enqueue_style('jquery-ui-css', 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.min.css'); + + wp_add_inline_script('jquery', ' + jQuery(document).ready(function($) { + + $(".notice-dismiss").on("click", function() { + $(this).closest(".notice").fadeOut(); + }); + + $(".advanced-toggle").click(function() { + $(".advanced-content").slideToggle(); + $(this).toggleClass("open"); + }); + + }); + '); + + wp_enqueue_script('rl-mailwarmer-public-script', RL_MAILWARMER_URL . '/js/public-check-domain-health.js', ['jquery'], null, true); + wp_localize_script('rl-mailwarmer-public-script', 'rlMailWarmer_public', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('check_domain_health_nonce'), + 'post_id' => get_the_ID() + ]); +} +add_action('wp_enqueue_scripts', 'rl_mailwarmer_enqueue_scripts'); + +/** + * Disable specific plugins based on the WP_ENVIRONMENT_TYPE + */ + +function disable_plugins_based_on_environment() { + // log_to_file("disable_plugins_based_on_environment - Disabling plugins"); + // Ensure the environment type is defined + if ( ! defined( 'WP_ENVIRONMENT_TYPE' ) ) { + return; + } + + // Retrieve the environment type + $environment = WP_ENVIRONMENT_TYPE; + + // log_to_file("disable_plugins_based_on_environment - Disabling plugins for $environment environment"); + + // Define plugins to disable based on environment in wp-config.php + $disabled_plugins = defined( 'DISABLED_PLUGINS' ) ? DISABLED_PLUGINS : array(); + + // Only proceed if the array is properly defined and not empty + if ( ! is_array( $disabled_plugins ) || empty( $disabled_plugins ) ) { + return; + } + + // Get the currently active plugins + $active_plugins = get_option( 'active_plugins', array() ); + + foreach ( $disabled_plugins as $plugin_env => $plugins ) { + // Skip if current environment doesn't match + if ( $environment !== $plugin_env ) { + continue; + } + + // Loop through plugins to disable for this environment + foreach ( $plugins as $plugin ) { + $plugin_key = array_search( $plugin, $active_plugins, true ); + + // If the plugin is active, deactivate it + if ( false !== $plugin_key ) { + unset( $active_plugins[ $plugin_key ] ); + } + } + } + + // Update the active plugins option + update_option( 'active_plugins', $active_plugins ); +} +add_action( 'plugins_loaded', 'disable_plugins_based_on_environment', 1 ); + +/** + * getRandomWeightedElement() + * Utility function for getting random values with weighting. + * Pass in an associative array, such as array('A'=>5, 'B'=>45, 'C'=>50) + * An array like this means that "A" has a 5% chance of being selected, "B" 45%, and "C" 50%. + * The return value is the array key, A, B, or C in this case. Note that the values assigned + * do not have to be percentages. The values are simply relative to each other. If one value + * weight was 2, and the other weight of 1, the value with the weight of 2 has about a 66% + * chance of being selected. Also note that weights should be integers. + * + * @param array $weightedValues + */ +function getRandomWeightedElement(array $weightedValues) { + $rand = mt_rand(1, (int) array_sum($weightedValues)); + + foreach ($weightedValues as $key => $value) { + $rand -= $value; + if ($rand <= 0) { + return $key; + } + } +} + +/** + * Retrieve the content of a textarea meta field and return it as an array if it's a comma-separated list or has multiple lines. + * + * @param int $post_id The ID of the post. + * @param string $meta_key The meta key to retrieve. + * @return array|string The processed array or the original string if no transformation is needed. + */ +function rl_get_textarea_meta_as_array($post_id, $meta_key) { + if ($post_id ==='option') { + $content = get_option('options_' . $meta_key); + } else { + $content = get_post_meta($post_id, $meta_key, true); + + } + + // log_to_file("rl_get_textarea_meta_as_array - Content {$content}"); + if (empty($content)) { + // log_to_file("rl_get_textarea_meta_as_array - {$meta_key} field not found for {$post_id}"); + return []; // Return an empty array if the meta field is empty + } + + // Check if the content contains multiple lines or is comma-separated + if (strpos($content, "\n") !== false || strpos($content, ',') !== false) { + // Normalize line breaks and split the content into an array + $content_array = preg_split('/[\r\n,]+/', $content); + + // Remove empty entries and trim whitespace + return array_filter(array_map('trim', $content_array)); + } + + return $content; // Return the original content if no transformation is needed +} + +/** + * + * Unset default dashboard widgets + * + */ + +function remove_dashboard_widgets() { + global $wp_meta_boxes; + + unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_right_now']); + unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_activity']); + unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_quick_press']); + unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_primary']); + // unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_incoming_links']); + // unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_plugins']); + // unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_recent_drafts']); + // unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_recent_comments']); + // unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_secondary']); + // unset($wp_meta_boxes['dashboard']['normal']['high']['rank_math_dashboard_widget']); +} + +add_action('wp_dashboard_setup', 'remove_dashboard_widgets' ); + +/** + * + * Reorder the admin menu. Leaves positions 1-8 available + * + */ + +add_action('admin_head', 'mf_edit_admin_menu'); +function mf_edit_admin_menu(){ + + global $menu; + + $menu[0] = $menu[2]; // Move the Dashboard to the top + unset($menu[2]); // Unset the Dashboard (from original position) + + // $menu[8] = $menu[4]; // Copy 'separator1' to a new menu location + unset($menu[4]); // Unset separator1 (from original position) + + ///$menu[9] = $menu[5]; // Copy 'Posts' to a new menu location + unset($menu[5]); // Unset Posts (from original position) + + ksort($menu); // Sort the menu + +} +/** + * Append Login In/Out link to menu with a redirect to this page + */ +add_filter( 'wp_nav_menu_primary-menu_items','rl_mailwarmer_loginout_menu_link', 10, 1 ); +function rl_mailwarmer_loginout_menu_link( $menu ) { + $referrer_url = $_SERVER['REQUEST_URI']; + // $redirect_url = "/membership-levels"; + $redirect_url = "/dashboard"; + // log_to_file("referrer_url: $referrer_url"); + // if ($referrer_url == '/membership-levels/') { + // $redirect_url = "/dashboard"; + // } + $loginout = wp_loginout( $redirect_url, false ); + $menu .= $loginout; + return $menu; +} + +// Create a shortcode to match +add_shortcode( 'mailferno_login_link', 'mailferno_show_login_link' ); +function mailferno_show_login_link( ) { + $referrer_url = $_SERVER['REQUEST_URI']; + // $redirect_url = "/membership-levels"; + $redirect_url = "/dashboard/?redirect_to=%2Fdashboard"; + // log_to_file("referrer_url: $referrer_url"); + // if ($referrer_url == '/membership-levels/') { + // $redirect_url = "/dashboard"; + // } + $login = 'Already have an account? Login here!'; + return $login; +} /** * Add a meta box for testing the SSH connection. */ + add_action('add_meta_boxes', function () { add_meta_box( 'test_ssh_connection_box', @@ -45,6 +263,7 @@ add_action('add_meta_boxes', function () { ); }); + /** * Render the SSH connection test meta box. * @@ -130,3 +349,223 @@ add_action('wp_ajax_rl_mailwarmer_test_ssh_connection', function () { }); + +/** + * Add sidebars to custom post types + * + */ + +function register_custom_sidebars() { + register_sidebar(array( + 'name' => 'Domain Sidebar', + 'id' => 'sidebar-domain', + 'description' => 'Sidebar for domain post type', + 'before_widget' => '', + 'before_title' => 'Timeline Generated: