monorepo/native/wordpress/wpforms-mailjet-automations/includes/class-wpfmj-form-handler.php

302 lines
10 KiB
PHP

<?php
/**
* Handle WPForms submissions and trigger Mailjet actions.
*
* @package WPFMJ
* @subpackage WPFMJ/includes
*/
class WPFMJ_Form_Handler {
/**
* Maximum retry attempts (configurable via filter).
*/
private $max_retries;
/**
* Constructor.
*/
public function __construct() {
// Allow filtering of max retry attempts (default 3, min 1, max 5)
$this->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}");
}
}
}
}