From c8002a41941112a9766c4a09486d54f823225e6c Mon Sep 17 00:00:00 2001 From: ruben Date: Wed, 4 Dec 2024 20:18:36 -0600 Subject: [PATCH] Added helper classes for campaigns, email, and db --- css/admin-style.css | 9 +- .../class-rl-mailwarmer-campaign-helper.php | 410 +++++ includes/class-rl-mailwarmer-db-helper.php | 111 ++ .../class-rl-mailwarmer-domain-helper.php | 96 +- includes/class-rl-mailwarmer-email-helper.php | 471 ++++++ includes/composer.json | 5 +- includes/composer.lock | 1394 ++++++++++++++++- includes/rl-mailwarmer-ajax.php | 49 + includes/rl-mailwarmer-domain-admin.php | 58 +- includes/rl-mailwarmer-email-admin.php | 131 ++ includes/rl-mailwarmer-functions.php | 136 +- js/campaign-timeline-heatmap.js | 65 + js/check-mail-login.js | 37 + js/email-account.js | 33 + js/generate-timeline.js | 35 + js/test-ssh-connection.js | 29 + rl-mailwarmer.php | 9 + 17 files changed, 3032 insertions(+), 46 deletions(-) create mode 100644 includes/class-rl-mailwarmer-campaign-helper.php create mode 100644 includes/class-rl-mailwarmer-db-helper.php create mode 100644 includes/class-rl-mailwarmer-email-helper.php create mode 100644 includes/rl-mailwarmer-email-admin.php create mode 100644 js/campaign-timeline-heatmap.js create mode 100644 js/check-mail-login.js create mode 100644 js/email-account.js create mode 100644 js/generate-timeline.js create mode 100644 js/test-ssh-connection.js diff --git a/css/admin-style.css b/css/admin-style.css index e9ab426..c011e53 100644 --- a/css/admin-style.css +++ b/css/admin-style.css @@ -40,4 +40,11 @@ button.button-primary:hover { table.rl_admin_meta_table { width: 100%; table-layout: fixed; -} \ No newline at end of file +} + +#campaign-timeline-heatmap { + width: 100%; /* Ensure it spans the available space */ + height: 300px; /* Set a fixed height */ + min-height: 300px; /* Ensure a minimum height */ + background-color: #f9f9f9; /* Optional: for better visibility during debugging */ +} diff --git a/includes/class-rl-mailwarmer-campaign-helper.php b/includes/class-rl-mailwarmer-campaign-helper.php new file mode 100644 index 0000000..3c621ba --- /dev/null +++ b/includes/class-rl-mailwarmer-campaign-helper.php @@ -0,0 +1,410 @@ + DateTime::createFromFormat('m/d/y', trim($date))->format('Y-m-d'), + explode("\n", $holidays_raw) + ); + + $min_starting_volume = (int) get_field('min_starting_email_volume', 'option') ?: 10; + $max_daily_volume = (int) get_field('max_campaign_daily_email_volume', 'option') ?: 1000; + + // Calculate starting daily volume (2.5% of target volume) + $starting_daily_volume = max(ceil($target_volume * 0.025), $min_starting_volume); + + // Initialize variables + $timeline = []; + $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; + + // Generate timeline + for ($day = 0; $day < $total_days; $day++) { + $current_date = clone $start_date; + $current_date->modify("+{$day} days"); + $date_formatted = $current_date->format('Y-m-d'); + + // 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; + + // Calculate daily volume + $daily_volume = min( + ceil(($starting_daily_volume + ($daily_increase * $day)) * $reduction_factor), + $target_volume * 1.2 + ); + + if ($daily_volume > $max_daily_volume) { + $daily_volume = $max_daily_volume; + } + + $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); + + 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')); + } + + // Insert the conversation into the database + $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')); + } + + return $conversation_id; + } + + /** + * Generate messages for a conversation. + * + * @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. + */ + 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')); + } + + $message_ids = []; + + 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 $message_ids; + } + + /** + * Generate conversations and messages for a campaign. + * + * @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. + */ + public static function generate_conversations($campaign_id, $weekly_volume) { + $conversation_ids = []; + + // 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]], + ]; + + foreach ($conversation_ratios as $ratio) { + $volume = ceil($weekly_volume * ($ratio['percentage'] / 100)); + + for ($i = 0; $i < $volume; $i++) { + $conversation_steps = []; // Generate steps based on ratio + $prompt = ''; // Generate prompt for AI (to be implemented) + + // Create conversation + $conversation_id = self::generate_conversation($campaign_id, $conversation_steps, $prompt); + + if (is_wp_error($conversation_id)) { + return $conversation_id; + } + + $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 []; + } + + /** + * Generate conversation vectors for the campaign. + * + * 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. + */ + public static function generate_campaign_conversation_vectors($campaign_id) + { + // Implementation placeholder + return []; + } + + /** + * Generate email conversation content using ChatGPT. + * + * 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. + */ + public static function generate_email_conversation($campaign_id, $vector) + { + // Implementation placeholder + return ''; + } +} + +/** + * Add a meta box for generating the campaign timeline. + */ +add_action('add_meta_boxes', function () { + add_meta_box( + 'generate_campaign_timeline', + __('Generate Timeline', 'rl-mailwarmer'), + 'rl_mailwarmer_render_generate_timeline_box', + 'campaign', + 'side', + 'default' + ); +}); + +/** + * Render the "Generate Timeline" meta box. + * + * @param WP_Post $post The current post object. + */ +function rl_mailwarmer_render_generate_timeline_box($post) +{ + // Add a nonce for security + wp_nonce_field('generate_timeline_nonce', 'generate_timeline_nonce_field'); + + // Render the form + ?> +
+

+ +

+
+
+ post_type === 'campaign') { + wp_enqueue_script( + 'rl-mailwarmer-generate-timeline-js', + RL_MAILWARMER_URL . 'js/generate-timeline.js', // Adjust path as needed + ['jquery'], + null, + true + ); + + wp_localize_script('rl-mailwarmer-generate-timeline-js', 'rlMailWarmerGenerateTimeline', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('generate_timeline_nonce'), + ]); + } +}); + +add_action('wp_ajax_rl_mailwarmer_generate_timeline', function () { + // Verify nonce + if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'generate_timeline_nonce')) { + wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); + } + + // Validate and sanitize input + $post_id = intval($_POST['post_id']); + if (!$post_id || get_post_type($post_id) !== 'campaign') { + wp_send_json_error(__('Invalid campaign ID.', 'rl-mailwarmer')); + } + + // Generate the timeline + try { + $timeline = RL_MailWarmer_Campaign_Helper::calculate_campaign_timeline($post_id); + // log_to_file("wp_ajax_rl_mailwarmer_generate_timeline - Generated Timeline: ", $timeline); + // update_post_meta($post_id, "campaign_timeline", $timeline); + wp_send_json_success($timeline); + } catch (Exception $e) { + wp_send_json_error($e->getMessage()); + } +}); + +// Cal-Heatmap + + + +add_action('edit_form_after_title', function ($post) { + if ($post->post_type === 'campaign') { + echo '
'; + } +}); +add_action('admin_enqueue_scripts', function ($hook) { + global $post; + + if (($hook === 'post.php' || $hook === 'post-new.php') && $post->post_type === 'campaign') { + // Enqueue D3.js (required for Cal-Heatmap) + wp_enqueue_script( + 'd3-js', + 'https://d3js.org/d3.v7.min.js', + [], + '7.0.0', + true + ); + + // Enqueue Cal-Heatmap + wp_enqueue_script( + 'cal-heatmap', + 'https://unpkg.com/cal-heatmap/dist/cal-heatmap.min.js', + ['d3-js'], + '4.2.4', + true + ); + + // Enqueue Cal-Heatmap CSS + wp_enqueue_style( + 'cal-heatmap', + 'https://unpkg.com/cal-heatmap/dist/cal-heatmap.css', + [], + '4.2.4' + ); + + // Custom heatmap script + wp_enqueue_script( + 'rl-mailwarmer-campaign-heatmap', + RL_MAILWARMER_URL . 'js/campaign-timeline-heatmap.js', + ['cal-heatmap', 'jquery'], + null, + true + ); + + // Pass data to the script + wp_localize_script('rl-mailwarmer-campaign-heatmap', 'rlMailWarmerHeatmap', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('campaign_timeline_nonce'), + 'post_id' => $post->ID, + ]); + } +}); + +add_action('wp_ajax_rl_mailwarmer_get_timeline', function () { + // Verify nonce + if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'campaign_timeline_nonce')) { + wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); + } + + // Validate and sanitize input + $post_id = intval($_POST['post_id']); + if (!$post_id || get_post_type($post_id) !== 'campaign') { + wp_send_json_error(__('Invalid campaign ID.', 'rl-mailwarmer')); + } + + // Retrieve the timeline + $timeline_json = get_post_meta($post_id, 'campaign_timeline', true); + $timeline = json_decode($timeline_json, true); + + if (!$timeline) { + wp_send_json_error(__('No timeline data found.', 'rl-mailwarmer')); + } + + // Convert timeline to Cal-Heatmap format (timestamp => value) + $heatmap_data = []; + foreach ($timeline as $date => $volume) { + $timestamp = strtotime($date); // Convert to UNIX timestamp + $heatmap_data[$timestamp] = $volume; + } + + wp_send_json_success($heatmap_data); +}); diff --git a/includes/class-rl-mailwarmer-db-helper.php b/includes/class-rl-mailwarmer-db-helper.php new file mode 100644 index 0000000..3aa37ab --- /dev/null +++ b/includes/class-rl-mailwarmer-db-helper.php @@ -0,0 +1,111 @@ +get_charset_collate(); + + // Conversation table + $conversation_sql = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}" . self::$conversation_table . "` ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + campaign_id BIGINT(20) UNSIGNED NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + conversation_steps JSON NOT NULL, + prompt TEXT NOT NULL, + PRIMARY KEY (id), + INDEX campaign_id_idx (campaign_id) + ) $charset_collate;"; + + // Message table + $message_sql = "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}" . self::$message_table . "` ( + id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + campaign_id BIGINT(20) UNSIGNED NOT NULL, + conversation_id BIGINT(20) UNSIGNED NOT NULL, + scheduled_for_timestamp DATETIME NOT NULL, + status ENUM('pending', 'in_progress', 'sent', 'failed') NOT NULL DEFAULT 'pending', + from_email VARCHAR(255) NOT NULL, + to_email TEXT NOT NULL, + cc TEXT NULL, + subject VARCHAR(255) NOT NULL, + body TEXT NOT NULL, + PRIMARY KEY (id), + INDEX scheduled_idx (scheduled_for_timestamp, status), + INDEX conversation_id_idx (conversation_id), + INDEX campaign_id_idx (campaign_id) + ) $charset_collate;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta($conversation_sql); + dbDelta($message_sql); + } + + /** + * Insert a conversation record. + */ + public static function insert_conversation($campaign_id, $conversation_steps, $prompt) { + global $wpdb; + + $wpdb->insert( + "{$wpdb->prefix}" . self::$conversation_table, + [ + 'campaign_id' => $campaign_id, + 'conversation_steps' => json_encode($conversation_steps), + 'prompt' => $prompt, + ], + ['%d', '%s', '%s'] + ); + + return $wpdb->insert_id; + } + + /** + * Insert a message record. + */ + public static function insert_message($campaign_id, $conversation_id, $scheduled_for, $from_email, $to_email, $cc, $subject, $body) { + global $wpdb; + + $wpdb->insert( + "{$wpdb->prefix}" . self::$message_table, + [ + 'campaign_id' => $campaign_id, + 'conversation_id' => $conversation_id, + 'scheduled_for_timestamp' => $scheduled_for, + 'status' => 'pending', + 'from_email' => $from_email, + 'to_email' => $to_email, + 'cc' => $cc, + 'subject' => $subject, + 'body' => $body, + ], + ['%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s'] + ); + + return $wpdb->insert_id; + } + + /** + * Fetch pending messages. + */ + public static function fetch_pending_messages($limit = 100) { + global $wpdb; + + $sql = $wpdb->prepare( + "SELECT * FROM `{$wpdb->prefix}" . self::$message_table . "` + WHERE scheduled_for_timestamp <= %s AND status = 'pending' + LIMIT %d", + current_time('mysql'), + $limit + ); + + return $wpdb->get_results($sql, ARRAY_A); + } +} + \ No newline at end of file diff --git a/includes/class-rl-mailwarmer-domain-helper.php b/includes/class-rl-mailwarmer-domain-helper.php index d0f7742..953fdc8 100644 --- a/includes/class-rl-mailwarmer-domain-helper.php +++ b/includes/class-rl-mailwarmer-domain-helper.php @@ -204,8 +204,11 @@ class RL_MailWarmer_Domain_Helper */ private static function backup_dns_record($record, $domain_id) { - // Define the backup storage method (e.g., custom table, file, or post type) - // Example: Saving backups as posts in a custom post type + + /* + * TODO: Move records to a separate table; only save a new backup if the changes are diff- + * erent than the last backup; add roll-back system + */ $post_data = [ 'post_type' => 'dns_record_backup', @@ -1118,5 +1121,94 @@ class RL_MailWarmer_Domain_Helper return $results; } + /* + * TO DO! + * + */ + // modify_domain_on_server - Creates/Updates/Deletes the specified domain on the specified server if it doesn't already exist (VirtualMin). Checks/Updates DNS config to include server IP in SPF, DKIM key, etc. + + /** + * Modify a domain account on a VirtualMin server. + * + * Creates, updates, or deletes a virtual server for the specified domain. + * + * @param int $domain_id The domain 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_domain_account_on_server($domain_id, $action) + { + // Validate domain post + $domain = get_post($domain_id); + if (!$domain || $domain->post_type !== 'domain') { + return new WP_Error('invalid_domain', __('Invalid domain post.', 'rl-mailwarmer')); + } + + // Fetch associated server posts + $server_ids = get_post_meta($domain_id, 'associated_servers', true); // Assume this holds server IDs + if (empty($server_ids) || !is_array($server_ids)) { + return new WP_Error('missing_servers', __('No associated servers found for the domain.', 'rl-mailwarmer')); + } + + $domain_name = $domain->post_title; + + // 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_user = get_post_meta($server_id, 'username', true); + $server_port = get_post_meta($server_id, 'ssh_port', true) ?: 22; + $server_password = get_post_meta($server_id, 'ssh_private_key', true); + + if (!$server_ip || !$server_user || !$server_password) { + return new WP_Error('missing_server_credentials', __('Missing server credentials.', 'rl-mailwarmer')); + } + + // Build VirtualMin command + $command = "virtualmin"; + if ($action === 'create') { + $command .= " create-domain --domain $domain_name --unix --dir --web --email"; + } elseif ($action === 'update') { + $command .= " modify-domain --domain $domain_name"; + } elseif ($action === 'delete') { + $command .= " delete-domain --domain $domain_name"; + } else { + return new WP_Error('invalid_action', __('Invalid action specified.', 'rl-mailwarmer')); + } + + // Execute the command via SSH + // $ssh = new phpseclib3\Net\SSH2($server_ip, $ssh_port); + // $key = phpseclib3\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_domain_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 + } + + } diff --git a/includes/class-rl-mailwarmer-email-helper.php b/includes/class-rl-mailwarmer-email-helper.php new file mode 100644 index 0000000..0450668 --- /dev/null +++ b/includes/class-rl-mailwarmer-email-helper.php @@ -0,0 +1,471 @@ + '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/composer.json b/includes/composer.json index 4dce3f3..e22e372 100644 --- a/includes/composer.json +++ b/includes/composer.json @@ -1,5 +1,8 @@ { "require": { - "guzzlehttp/guzzle": "^7.9" + "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 index 5f58efd..d0608e0 100644 --- a/includes/composer.lock +++ b/includes/composer.lock @@ -4,8 +4,200 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7827c548fdcc7e87cb0ae341dd2c6b1b", + "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", @@ -331,6 +523,336 @@ ], "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", @@ -491,6 +1013,56 @@ }, "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", @@ -601,6 +1173,826 @@ } ], "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": [], diff --git a/includes/rl-mailwarmer-ajax.php b/includes/rl-mailwarmer-ajax.php index ba9663a..ccd435c 100644 --- a/includes/rl-mailwarmer-ajax.php +++ b/includes/rl-mailwarmer-ajax.php @@ -177,3 +177,52 @@ add_action('wp_ajax_rl_mailwarmer_create_dns_backup', function () { wp_send_json_error($e->getMessage()); } }); + +add_action('wp_ajax_rl_mailwarmer_modify_email_account', function () { + // Verify nonce + if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'modify_email_account_nonce')) { + wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); + } + + // Prepare arguments for modify_email_account + $args = [ + 'action' => sanitize_text_field($_POST['action_type']), + 'email' => sanitize_email($_POST['email_address']), + 'metadata' => [ + 'mail_password' => sanitize_text_field($_POST['mail_password']), + 'email_provider' => intval($_POST['email_provider']), + 'imap_server' => sanitize_text_field($_POST['imap_server']), + ], + ]; + + // Call modify_email_account + try { + $result = RL_MailWarmer_Email_Helper::modify_email_account($args); + wp_send_json_success('Email account successfully modified. Post ID: ' . $result); + } catch (Exception $e) { + wp_send_json_error($e->getMessage()); + } +}); + +add_action('wp_ajax_rl_mailwarmer_check_mail_login', function () { + // Verify nonce + if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'check_mail_login_nonce')) { + wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); + } + + // Validate and sanitize input + $post_id = intval($_POST['post_id']); + $protocol = !empty($_POST['protocol']) ? sanitize_text_field($_POST['protocol']) : null; + + if (!$post_id || get_post_type($post_id) !== 'email-account') { + wp_send_json_error(__('Invalid email account ID.', 'rl-mailwarmer')); + } + + // Call check_mail_login + try { + $results = RL_MailWarmer_Email_Helper::check_mail_login($post_id, $protocol); + wp_send_json_success($results); + } catch (Exception $e) { + wp_send_json_error($e->getMessage()); + } +}); diff --git a/includes/rl-mailwarmer-domain-admin.php b/includes/rl-mailwarmer-domain-admin.php index 26750cd..692454b 100644 --- a/includes/rl-mailwarmer-domain-admin.php +++ b/includes/rl-mailwarmer-domain-admin.php @@ -46,7 +46,63 @@ add_action('admin_enqueue_scripts', function ($hook) { 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('create_dns_backup_nonce'), ]); - } + } else if ($post->post_type === 'email-account') { + wp_enqueue_script( + 'rl-mailwarmer-check-login-js', + RL_MAILWARMER_URL . 'js/check-mail-login.js', // Adjust path as needed + ['jquery'], + null, + true + ); + + wp_localize_script('rl-mailwarmer-check-login-js', 'rlMailWarmerCheckMailLogin', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('check_mail_login_nonce'), + 'post_id' => $post->ID + ]); + } else if ($post->post_type === 'campaign') { + // log_to_file("On a campaign page. Enqueing admin scripts"); + // // Enqueue D3.js + // wp_enqueue_script( + // 'd3-js', + // 'https://d3js.org/d3.v7.min.js', + // [], + // '7.0.0', + // true + // ); + + // // Enqueue Cal-Heatmap script and style + // wp_enqueue_script( + // 'cal-heatmap', + // 'https://unpkg.com/cal-heatmap/dist/cal-heatmap.min.js', + // [], + // '4.2.4', + // true + // ); + + // wp_enqueue_style( + // 'cal-heatmap', + // 'https://unpkg.com/cal-heatmap/dist/cal-heatmap.css', + // [], + // '4.2.4' + // ); + + // // Enqueue custom script for rendering the heatmap + // wp_enqueue_script( + // 'rl-mailwarmer-campaign-heatmap', + // RL_MAILWARMER_URL . 'js/campaign-timeline-heatmap.js', // Adjust the path as needed + // ['cal-heatmap', 'jquery'], + // null, + // true + // ); + + // // Pass data to the custom script + // wp_localize_script('rl-mailwarmer-campaign-heatmap', 'rlMailWarmerHeatmap', [ + // 'ajax_url' => admin_url('admin-ajax.php'), + // 'nonce' => wp_create_nonce('campaign_timeline_nonce'), + // 'post_id' => $post->ID, + // ]); + } } }); diff --git a/includes/rl-mailwarmer-email-admin.php b/includes/rl-mailwarmer-email-admin.php new file mode 100644 index 0000000..94b8d00 --- /dev/null +++ b/includes/rl-mailwarmer-email-admin.php @@ -0,0 +1,131 @@ + admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('modify_email_account_nonce'), + ]); +}); + + +/** + * Add a custom metabox to the WP dashboard. + */ +add_action('wp_dashboard_setup', function () { + wp_add_dashboard_widget( + 'modify_email_account_widget', // Widget ID + __('Modify Email Account', 'rl-mailwarmer'), // Title + 'rl_mailwarmer_render_email_account_widget' // Callback function + ); +}); + +/** + * Add a meta box for checking mail login. + */ +add_action('add_meta_boxes', function () { + add_meta_box( + 'check_mail_login_box', + __('Check Mail Login', 'rl-mailwarmer'), + 'rl_mailwarmer_render_check_mail_login_box', + 'email-account', + 'side', + 'default' + ); +}); + +/** + * Render the Modify Email Account metabox. + */ +function rl_mailwarmer_render_email_account_widget() +{ + // Fetch email providers + $email_providers = get_posts([ + 'post_type' => 'email-provider', + 'post_status' => 'publish', + 'numberposts' => -1, + 'orderby' => 'title', + 'order' => 'ASC', + ]); + + // Render the form + ?> +
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ +

+
+
+ +
+

+ + +

+

+ +

+
+
+ post_type !== 'domain') { -// return; -// } -// // Only run when the status changes to 'publish' from a non-published status -// if ($new_status === 'publish' && $old_status !== 'publish') { -// try { -// RL_MailWarmer_Domain_Helper::saveDomainHealthReport($post->ID); -// } catch (Exception $e) { -// error_log('Failed to save domain health report: ' . $e->getMessage()); -// } -// } -// }, 10, 3); + /** - * Save a domain health report when a new domain is created. - * - * @param int $post_id The ID of the post being saved. - * @param WP_Post $post The post object. - * @param bool $update Whether this is an update. + * Add a meta box for testing the SSH connection. */ -// add_action('save_post_domain', function ($post_id, $post, $update) { -// // Exclude autosaves, revisions, drafts, and updates -// if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id) || $post->post_status === 'draft' || $update) { -// return; -// } +add_action('add_meta_boxes', function () { + add_meta_box( + 'test_ssh_connection_box', + __('Test SSH Connection', 'rl-mailwarmer'), + 'rl_mailwarmer_render_test_ssh_connection_box', + 'server', + 'side', + 'default' + ); +}); + +/** + * Render the SSH connection test meta box. + * + * @param WP_Post $post The current post object. + */ +function rl_mailwarmer_render_test_ssh_connection_box($post) +{ + // Add a nonce field for security + wp_nonce_field('test_ssh_connection_nonce', 'test_ssh_connection_nonce_field'); + + // Render the button + ?> +
+

+ +

+
+
+ post_type === 'server') { + wp_enqueue_script( + 'rl-mailwarmer-test-ssh-js', + RL_MAILWARMER_URL . 'js/test-ssh-connection.js', // Adjust path as needed + ['jquery'], + null, + true + ); + + wp_localize_script('rl-mailwarmer-test-ssh-js', 'rlMailWarmerTestSSH', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('test_ssh_connection_nonce'), + 'post_id' => $post->ID + ]); + } +}); + +add_action('wp_ajax_rl_mailwarmer_test_ssh_connection', function () { + // Verify nonce + if (!isset($_POST['security']) || !wp_verify_nonce($_POST['security'], 'test_ssh_connection_nonce')) { + wp_send_json_error(__('Invalid nonce', 'rl-mailwarmer')); + } + + // Validate and sanitize input + $post_id = intval($_POST['post_id']); + if (!$post_id || get_post_type($post_id) !== 'server') { + wp_send_json_error(__('Invalid server ID.', 'rl-mailwarmer')); + } + + // Fetch server details + $server_ip = get_post_meta($post_id, 'ip_address', true); + $server_port = get_post_meta($post_id, 'ssh_port', true); + $server_user = get_post_meta($post_id, 'username', true); + $server_key = get_post_meta($post_id, 'ssh_private_key', true); + + // log_to_file("wp_ajax_rl_mailwarmer_test_ssh_connection - SSH $server_user $server_ip $server_port"); + // log_to_file("wp_ajax_rl_mailwarmer_test_ssh_connection - SSH Private Key: $server_key"); + + if (empty($server_ip) || empty($server_user) || empty($server_key)) { + wp_send_json_error(__('Missing server credentials.', 'rl-mailwarmer')); + } + + // Test SSH connection + try { + $ssh = new phpseclib3\Net\SSH2($server_ip, $server_port); + $key = phpseclib3\Crypt\PublicKeyLoader::loadPrivateKey($server_key); + + if (!$ssh->login($server_user, $key)) { + throw new Exception(__('SSH login failed.', 'rl-mailwarmer')); + } + + wp_send_json_success(__('SSH connection successful.', 'rl-mailwarmer')); + } catch (Exception $e) { + wp_send_json_error($e->getMessage()); + } +}); -// // Call saveDomainHealthReport -// try { -// log_to_file("save_post_domain - Running health report for newly added domain: " . $post->post_title); -// RL_MailWarmer_Domain_Helper::saveDomainHealthReport($post_id); -// } catch (Exception $e) { -// error_log('Failed to save domain health report: ' . $e->getMessage()); -// } -// log_to_file("save_post_domain - Finished! " . $post->post_title); -// }, 10, 3); diff --git a/js/campaign-timeline-heatmap.js b/js/campaign-timeline-heatmap.js new file mode 100644 index 0000000..985f874 --- /dev/null +++ b/js/campaign-timeline-heatmap.js @@ -0,0 +1,65 @@ +jQuery(document).ready(function ($) { + // Wait for the DOM to be fully loaded + $(window).on('load', function () { + // Ensure the element exists before proceeding + const heatmapContainer = $('#campaign-timeline-heatmap'); + if (!heatmapContainer.length) { + console.error('Heatmap container not found.'); + return; + } + + // Fetch campaign timeline data via AJAX + $.ajax({ + url: rlMailWarmerHeatmap.ajax_url, + method: 'POST', + data: { + action: 'rl_mailwarmer_get_timeline', + post_id: rlMailWarmerHeatmap.post_id, + security: rlMailWarmerHeatmap.nonce, + }, + success: function (response) { + if (response.success) { + console.log(response.data); + // Initialize the heatmap + const cal = new CalHeatmap(); + cal.paint({ + data: { + source: response.data, + x: 'date', + y: d => +d['volume'], + }, + date: { + start: new Date('2024-12-4'), + }, + range: 1, // Number of months to display + domain: { type: 'year' }, + subDomain: { type: 'day' }, // Granularity: days + legend: { + show: true, + label: 'Daily Volume', + width: 150, + marginLeft: 10, + marginRight: 10, + }, + scale: { + color: { + range: ['yellow', 'red'], + interpolate: 'hsl', + type: 'quantize', + domain: [0, 150], + scheme: 'YlOrRd', + }, + }, + verticalOrientation: true, + itemSelector: '#campaign-timeline-heatmap', + }); + } else { + heatmapContainer.html('

Error: ' + response.data + '

'); + } + }, + error: function () { + heatmapContainer.html('

Failed to load timeline data.

'); + }, + }); + }); +}); diff --git a/js/check-mail-login.js b/js/check-mail-login.js new file mode 100644 index 0000000..dabb6e0 --- /dev/null +++ b/js/check-mail-login.js @@ -0,0 +1,37 @@ +jQuery(document).ready(function ($) { + $('#check-mail-login-button').on('click', function (e) { + e.preventDefault(); + + const postId = $('#post_ID').val(); + const protocol = $('#protocol').val(); + + $('#check-mail-login-result').html('

Checking...

'); + + $.ajax({ + url: rlMailWarmerCheckMailLogin.ajax_url, + method: 'POST', + data: { + action: 'rl_mailwarmer_check_mail_login', + post_id: postId, + protocol: protocol, + security: rlMailWarmerCheckMailLogin.nonce, + }, + success: function (response) { + if (response.success) { + const results = response.data; + let output = '

Check Results:

'; + $('#check-mail-login-result').html(output); + } else { + $('#check-mail-login-result').html('

Error: ' + response.data + '

'); + } + }, + error: function (xhr, status, error) { + $('#check-mail-login-result').html('

AJAX Error: ' + error + '

'); + }, + }); + }); +}); diff --git a/js/email-account.js b/js/email-account.js new file mode 100644 index 0000000..fd4919b --- /dev/null +++ b/js/email-account.js @@ -0,0 +1,33 @@ +jQuery(document).ready(function ($) { + $('#submit-email-account').on('click', function (e) { + e.preventDefault(); + + const formData = { + action: 'rl_mailwarmer_modify_email_account', + security: rlMailWarmerEmailAccount.nonce, + email_address: $('#email_address').val(), + mail_password: $('#mail_password').val(), + action_type: $('#action').val(), + email_provider: $('#email_provider').val(), + imap_server: $('#imap_server').val(), + }; + + $('#modify-email-account-result').html('

Processing...

'); + + $.ajax({ + url: rlMailWarmerEmailAccount.ajax_url, + method: 'POST', + data: formData, + success: function (response) { + if (response.success) { + $('#modify-email-account-result').html('

Success: ' + response.data + '

'); + } else { + $('#modify-email-account-result').html('

Error: ' + response.data + '

'); + } + }, + error: function (xhr, status, error) { + $('#modify-email-account-result').html('

AJAX Error: ' + error + '

'); + }, + }); + }); +}); diff --git a/js/generate-timeline.js b/js/generate-timeline.js new file mode 100644 index 0000000..47b1274 --- /dev/null +++ b/js/generate-timeline.js @@ -0,0 +1,35 @@ +jQuery(document).ready(function ($) { + $('#generate-timeline-button').on('click', function (e) { + e.preventDefault(); + + const postId = $('#post_ID').val(); + + $('#generate-timeline-result').html('

Generating timeline...

'); + + $.ajax({ + url: rlMailWarmerGenerateTimeline.ajax_url, + method: 'POST', + data: { + action: 'rl_mailwarmer_generate_timeline', + post_id: postId, + security: rlMailWarmerGenerateTimeline.nonce, + }, + success: function (response) { + if (response.success) { + const timeline = response.data; + let output = '

Timeline Generated:

'; + $('#generate-timeline-result').html(output); + } else { + $('#generate-timeline-result').html('

Error: ' + response.data + '

'); + } + }, + error: function (xhr, status, error) { + $('#generate-timeline-result').html('

AJAX Error: ' + error + '

'); + }, + }); + }); +}); diff --git a/js/test-ssh-connection.js b/js/test-ssh-connection.js new file mode 100644 index 0000000..59e47c4 --- /dev/null +++ b/js/test-ssh-connection.js @@ -0,0 +1,29 @@ +jQuery(document).ready(function ($) { + $('#test-ssh-connection-button').on('click', function (e) { + e.preventDefault(); + + const postId = rlMailWarmerTestSSH.post_id; + + $('#test-ssh-connection-result').html('

Testing connection...

'); + + $.ajax({ + url: rlMailWarmerTestSSH.ajax_url, + method: 'POST', + data: { + action: 'rl_mailwarmer_test_ssh_connection', + post_id: postId, + security: rlMailWarmerTestSSH.nonce, + }, + success: function (response) { + if (response.success) { + $('#test-ssh-connection-result').html('

Success: ' + response.data + '

'); + } else { + $('#test-ssh-connection-result').html('

Error: ' + response.data + '

'); + } + }, + error: function (xhr, status, error) { + $('#test-ssh-connection-result').html('

AJAX Error: ' + error + '

'); + }, + }); + }); +}); diff --git a/rl-mailwarmer.php b/rl-mailwarmer.php index 56af6a7..d5a10e8 100644 --- a/rl-mailwarmer.php +++ b/rl-mailwarmer.php @@ -27,10 +27,16 @@ require_once RL_MAILWARMER_PATH . 'includes/rl-mailwarmer-functions.php'; require_once RL_MAILWARMER_PATH . 'includes/rl-mailwarmer-ajax.php'; require_once RL_MAILWARMER_PATH . 'includes/rl-mailwarmer-rest.php'; require_once RL_MAILWARMER_PATH . 'includes/rl-mailwarmer-domain-admin.php'; +require_once RL_MAILWARMER_PATH . 'includes/rl-mailwarmer-email-admin.php'; +require_once RL_MAILWARMER_PATH . 'includes/class-rl-mailwarmer-db-helper.php'; require_once RL_MAILWARMER_PATH . 'includes/class-rl-mailwarmer-domain-helper.php'; +require_once RL_MAILWARMER_PATH . 'includes/class-rl-mailwarmer-email-helper.php'; +require_once RL_MAILWARMER_PATH . 'includes/class-rl-mailwarmer-campaign-helper.php'; require_once RL_MAILWARMER_PATH . 'includes/class-rl-mailwarmer-scheduler.php'; require_once RL_MAILWARMER_PATH . 'includes/class-rl-mailwarmer-email-handler.php'; +// require_once RL_MAILWARMER_PATH . 'includes/vendor/autoload.php'; + /** * Initialize the plugin. */ @@ -55,6 +61,9 @@ function rl_mailwarmer_activate() { // Schedule cron jobs on activation RL_MailWarmer_Scheduler::schedule_cron_jobs(); + + // Create custom tables + RL_MailWarmer_DB_Helper::create_tables(); } register_activation_hook(__FILE__, 'rl_mailwarmer_activate');