max_retries = apply_filters('wpfmj_max_retry_attempts', 3); $this->max_retries = max(1, min(5, absint($this->max_retries))); } /** * Handle form submission. */ public function handle_submission($fields, $entry, $form_data, $entry_id) { $form_id = $form_data['id']; // Find all active automations for this form $automations = $this->get_active_automations($form_id); if (empty($automations)) { return; } foreach ($automations as $automation) { $this->process_automation($automation, $fields, $entry_id); } } /** * Get active automations for a form. */ private function get_active_automations($form_id) { $args = array( 'post_type' => 'wpfmj_automation', 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_query' => array( array( 'key' => '_wpfmj_form_id', 'value' => $form_id, 'compare' => '=' ) ) ); return get_posts($args); } /** * Process a single automation. */ private function process_automation($automation, $fields, $entry_id) { $automation_id = $automation->ID; // Get automation settings $config = get_post_meta($automation_id, '_wpfmj_config', true); if (empty($config)) { $this->log_error($automation_id, $entry_id, 'missing_config', 'Automation configuration is empty'); return; } // Validate config structure for data integrity $required_keys = array('field_mapping', 'trigger_field_id', 'api_key', 'api_secret', 'list_mappings'); foreach ($required_keys as $key) { if (!isset($config[$key])) { $this->log_error($automation_id, $entry_id, 'invalid_config', "Missing required config key: {$key}"); return; } } if (!isset($config['field_mapping']['email'])) { $this->log_error($automation_id, $entry_id, 'invalid_config', 'Email field mapping is missing'); return; } if (!is_array($config['list_mappings']) || empty($config['list_mappings'])) { $this->log_error($automation_id, $entry_id, 'invalid_config', 'List mappings are empty or invalid'); return; } // Extract form data $email = $this->get_field_value($fields, $config['field_mapping']['email']); $firstname = $this->get_field_value($fields, $config['field_mapping']['firstname']); $lastname = $this->get_field_value($fields, $config['field_mapping']['lastname']); $trigger_value = $this->get_field_value($fields, $config['trigger_field_id']); // Validate email address if (empty($email)) { $this->log_error($automation_id, $entry_id, 'missing_email', 'Email field is empty'); return; } // Sanitize and validate email format $email = sanitize_email($email); if (!is_email($email)) { $this->log_error($automation_id, $entry_id, 'invalid_email', 'Email address is not valid: ' . sanitize_text_field($email)); return; } // Determine which lists to add the contact to based on trigger value $lists_to_add = $this->get_lists_for_trigger_value($trigger_value, $config['list_mappings']); if (empty($lists_to_add)) { // No matching lists, this is normal behavior return; } // Prepare contact properties $properties = array(); if (!empty($firstname)) { $properties['firstname'] = $firstname; } if (!empty($lastname)) { $properties['lastname'] = $lastname; } // Add to Mailjet with retry logic $this->add_to_mailjet_with_retry($automation_id, $entry_id, $email, $lists_to_add, $properties, $config['api_key'], $config['api_secret']); } /** * Get field value from fields array. */ private function get_field_value($fields, $field_id) { if (!isset($fields[$field_id])) { return ''; } $value = $fields[$field_id]['value']; // Handle arrays (checkboxes, multi-select) if (is_array($value)) { return $value; } return sanitize_text_field($value); } /** * Get lists for trigger value. */ private function get_lists_for_trigger_value($trigger_value, $mappings) { $lists = array(); // Handle array values (checkboxes, multi-select) if (is_array($trigger_value)) { foreach ($trigger_value as $value) { if (isset($mappings[$value])) { $lists[] = $mappings[$value]; } } } else { // Single value (radio, dropdown) if (isset($mappings[$trigger_value])) { $lists[] = $mappings[$trigger_value]; } } return array_unique(array_filter($lists)); } /** * Add to Mailjet with retry logic. */ private function add_to_mailjet_with_retry($automation_id, $entry_id, $email, $lists, $properties, $api_key, $api_secret) { $decrypted_key = WPFMJ_Encryption::decrypt($api_key); $decrypted_secret = WPFMJ_Encryption::decrypt($api_secret); // Check for decryption failures (can happen if encryption key changed) if ($decrypted_key === false || $decrypted_secret === false || empty($decrypted_key) || empty($decrypted_secret)) { $this->log_error( $automation_id, $entry_id, 'decryption_failed', 'Failed to decrypt API credentials. The encryption key may have changed. Please re-save this automation with valid API credentials.' ); $this->notify_admin_of_failure($automation_id, $entry_id, 'API credential decryption failed'); return; } $api = new WPFMJ_Mailjet_API($decrypted_key, $decrypted_secret); $attempt = 0; $success = false; while ($attempt < $this->max_retries && !$success) { $result = $api->add_contact_to_lists($email, $lists, $properties); if (!is_wp_error($result)) { $success = true; // Log success do_action('wpfmj_automation_success', $automation_id, $entry_id, $email, $lists); } else { $attempt++; if ($attempt < $this->max_retries) { // Exponential backoff: 1s, 2s, 4s sleep(pow(2, $attempt - 1)); } else { // Max retries reached, log error $this->log_error( $automation_id, $entry_id, 'mailjet_api_error', $result->get_error_message(), $attempt ); // Notify admin $this->notify_admin_of_failure($automation_id, $entry_id, $result->get_error_message()); } } } } /** * Log error. */ private function log_error($automation_id, $entry_id, $error_type, $error_message, $retry_count = 0) { $logger = new WPFMJ_Error_Logger(); $logger->log($automation_id, $entry_id, $error_type, $error_message, $retry_count); } /** * Notify admin of failure. */ private function notify_admin_of_failure($automation_id, $entry_id, $error_message) { // Check if notifications are disabled if (apply_filters('wpfmj_disable_failure_notifications', false)) { return; } // Get notification recipients (filterable) $default_email = get_option('admin_email'); $recipients = apply_filters('wpfmj_failure_notification_emails', array($default_email)); // Ensure recipients is an array if (!is_array($recipients)) { $recipients = array($default_email); } // Remove invalid emails $recipients = array_filter($recipients, function($email) { return is_email($email); }); if (empty($recipients)) { error_log('WPFMJ: No valid email recipients for failure notification'); return; } $automation_title = get_the_title($automation_id); $site_name = get_bloginfo('name'); // Sanitize all data for email $automation_title = sanitize_text_field($automation_title); $site_name = sanitize_text_field($site_name); $error_message = sanitize_textarea_field($error_message); $entry_id = intval($entry_id); $subject = sprintf( '[%s] Mailjet Automation Failed', $site_name ); $message = sprintf( "A Mailjet automation has failed after %d retry attempts.\n\nAutomation: %s\nEntry ID: %d\nError: %s\n\nPlease check the error logs in your WordPress admin.", $this->max_retries, $automation_title, $entry_id, $error_message ); // Use wp_mail with proper headers $headers = array('Content-Type: text/plain; charset=UTF-8'); // Send to all recipients foreach ($recipients as $recipient) { $sent = wp_mail($recipient, $subject, $message, $headers); if (!$sent) { error_log("WPFMJ: Failed to send notification email to {$recipient}"); } } } }