mxchat_init_ajax_hooks(); } /** * Register all AJAX action hooks */ private function mxchat_init_ajax_hooks() { // Settings AJAX add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback')); add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback')); add_action('wp_ajax_migrate_pinecone_settings', array($this, 'ajax_migrate_pinecone_settings')); // License AJAX add_action('wp_ajax_mxchat_handle_activate_license', array($this, 'mxchat_handle_activate_license')); add_action('wp_ajax_mxchat_check_license_status', array($this, 'mxchat_check_license_status')); add_action('wp_ajax_mxchat_deactivate_license', array($this, 'mxchat_deactivate_license')); // Actions & Intents AJAX add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action')); add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold')); add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot')); } // ======================================== // SETTINGS AJAX HANDLERS // ======================================== /** * Validates and saves chat settings via AJAX request */ public function mxchat_save_setting_callback() { check_ajax_referer('mxchat_save_setting_nonce'); if (!current_user_can('manage_options')) { ('MXChat Save: Unauthorized access attempt'); wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]); } $name = isset($_POST['name']) ? $_POST['name'] : ''; // Strip slashes from the value before saving $value = isset($_POST['value']) ? stripslashes($_POST['value']) : ''; //error_log('MXChat Save: Processing field name: ' . $name); //error_log('MXChat Save: Field value: ' . $value); if (empty($name)) { //error_log('MXChat Save: Empty field name detected'); wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]); } // Load the full options array $options = get_option('mxchat_options', []); //error_log('MXChat Save: Current options array: ' . print_r($options, true)); // Handle special cases switch ($name) { case 'additional_popular_questions': //error_log('MXChat Save: Processing additional_popular_questions'); $questions = json_decode($value, true); // No need for stripslashes here if (is_array($questions)) { $options[$name] = $questions; // Also update old option for backwards compatibility update_option('additional_popular_questions', $questions); //error_log('MXChat Save: Saved ' . count($questions) . ' additional questions'); } else { //error_log('MXChat Save: Failed to decode questions JSON'); } break; case 'email_blocker_header_content': //error_log('MXChat Save: Processing email_blocker_header_content'); // Allow HTML content but sanitize it safely $options[$name] = wp_kses_post($value); break; case 'email_blocker_button_text': //error_log('MXChat Save: Processing email_blocker_button_text'); $options[$name] = sanitize_text_field($value); break; case 'name_field_placeholder': //error_log('MXChat Save: Processing name_field_placeholder'); $options[$name] = sanitize_text_field($value); break; case 'similarity_threshold': //error_log('MXChat Save: Processing similarity_threshold'); // Save to the options array $options[$name] = $value; break; case 'user_message_bg_color': case 'user_message_font_color': case 'bot_message_bg_color': case 'bot_message_font_color': case 'top_bar_bg_color': case 'send_button_font_color': case 'chatbot_background_color': case 'icon_color': case 'chat_input_font_color': case 'live_agent_message_bg_color': case 'live_agent_message_font_color': case 'mode_indicator_bg_color': case 'mode_indicator_font_color': case 'toolbar_icon_color': case 'quick_questions_toggle_color': //error_log('MXChat Save: Processing color value: ' . $name); // Store color values directly $options[$name] = $value; break; case 'live_agent_status': //error_log('MXChat Save: Processing live_agent_status'); // Set the new value $options[$name] = ($value === 'on') ? 'on' : 'off'; break; case 'enable_woocommerce_integration': //error_log('MXChat Save: Processing enable_woocommerce_integration'); // Handle values that used to be 1/0 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off'; break; default: // First check for rate limits settings if (strpos($name, 'mxchat_options[rate_limits]') !== false) { //error_log('MXChat Save: Detected rate_limits field: ' . $name); // Extract role ID and setting from the name preg_match('/\[rate_limits\]\[(.*?)\]\[(.*?)\]/', $name, $matches); //error_log('MXChat Save: Regex matches: ' . print_r($matches, true)); if (isset($matches[1]) && isset($matches[2])) { $role_id = $matches[1]; $setting_key = $matches[2]; // limit, timeframe, or message //error_log('MXChat Save: Role ID = ' . $role_id . ', Setting Key = ' . $setting_key); // Initialize rate_limits if it doesn't exist if (!isset($options['rate_limits'])) { // //error_log('MXChat Save: Initializing rate_limits array'); $options['rate_limits'] = []; } // Initialize role settings if it doesn't exist if (!isset($options['rate_limits'][$role_id])) { //error_log('MXChat Save: Initializing rate_limits for role: ' . $role_id); $options['rate_limits'][$role_id] = [ 'limit' => ($role_id === 'logged_out') ? '10' : '100', 'timeframe' => 'daily', 'message' => 'Rate limit exceeded. Please try again later.' ]; } // Update the specific setting $options['rate_limits'][$role_id][$setting_key] = $value; //error_log('MXChat Save: Updated rate_limits[' . $role_id . '][' . $setting_key . '] = ' . $value); } else { //error_log('MXChat Save: Failed to parse rate_limits pattern: ' . $name); } } // Then check for role rate limits (old format) else if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) { //error_log('MXChat Save: Processing role_rate_limits field: ' . $name); // Extract role ID from the name preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches); //error_log('MXChat Save: Regex matches: ' . print_r($matches, true)); if (isset($matches[1])) { $role_id = $matches[1]; // Initialize role_rate_limits if it doesn't exist if (!isset($options['role_rate_limits'])) { //error_log('MXChat Save: Initializing role_rate_limits array'); $options['role_rate_limits'] = []; } // Update the specific role's rate limit $options['role_rate_limits'][$role_id] = sanitize_text_field($value); //error_log('MXChat Save: Updated role_rate_limits[' . $role_id . '] = ' . $value); } else { //error_log('MXChat Save: Failed to parse role_rate_limits pattern: ' . $name); } } // Handle toggles else if (strpos($name, 'toggle') !== false || in_array($name, [ 'chat_persistence_toggle', 'privacy_toggle', 'complianz_toggle', 'chat_toolbar_toggle', 'show_pdf_upload_button', 'show_word_upload_button', 'enable_streaming_toggle', 'contextual_awareness_toggle', 'enable_email_block', 'enable_name_field' ])) { //error_log('MXChat Save: Processing toggle: ' . $name); $options[$name] = ($value === 'on') ? 'on' : 'off'; } else { //error_log('MXChat Save: Processing standard field: ' . $name); // Store all other values directly $options[$name] = $value; } break; } // Save all updates to the options array $updated = update_option('mxchat_options', $options); //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name); //error_log('MXChat Save: Updated options array: ' . print_r($options, true)); // Always return success even if WordPress says nothing changed // (which happens when the value is the same as before) wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]); } /** * Save the selected bot for knowledge base operations */ public function mxchat_save_selected_bot() { // Check nonce if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) { wp_send_json_error('Invalid nonce'); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized'); } $bot_id = isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default'; // Save as user meta for the current user $user_id = get_current_user_id(); update_user_meta($user_id, 'mxchat_selected_knowledge_bot', $bot_id); // Also save as an option for site-wide default update_option('mxchat_current_knowledge_bot', $bot_id); // No cache clearing needed since we removed caching wp_send_json_success(array( 'message' => 'Bot selection saved', 'bot_id' => $bot_id )); } /** * Handles AJAX request for saving chat settings */ public function mxchat_save_prompts_setting_callback() { check_ajax_referer('mxchat_prompts_setting_nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]); } $name = isset($_POST['name']) ? $_POST['name'] : ''; $value = isset($_POST['value']) ? stripslashes($_POST['value']) : ''; //error_log('[MXCHAT-PROMPTS] Saving setting: ' . $name . ' = ' . $value); if (empty($name)) { wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]); } // Handle Pinecone settings - BYPASS WORDPRESS SANITIZATION if (strpos($name, 'mxchat_pinecone_addon_options') !== false) { //error_log('[MXCHAT-PROMPTS] Processing Pinecone setting: ' . $name); // Extract the field name if (preg_match('/mxchat_pinecone_addon_options\[([^\]]+)\]/', $name, $matches)) { $field_name = $matches[1]; //error_log('[MXCHAT-PROMPTS] Extracted field name: ' . $field_name); // Get current options directly from database - NO WordPress filters global $wpdb; $current_options_raw = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", 'mxchat_pinecone_addon_options' ) ); // FIX: Handle the case where the option doesn't exist yet if ($current_options_raw === null) { // Option doesn't exist, create it with default values $current_options = array( 'mxchat_use_pinecone' => '0', 'mxchat_pinecone_api_key' => '', 'mxchat_pinecone_host' => '', 'mxchat_pinecone_index' => '', 'mxchat_pinecone_environment' => '' ); //error_log('[MXCHAT-PROMPTS] Option does not exist, creating with defaults'); } else { // Unserialize the raw data $current_options = maybe_unserialize($current_options_raw); if (!is_array($current_options)) { // Fallback to defaults if unserialization fails $current_options = array( 'mxchat_use_pinecone' => '0', 'mxchat_pinecone_api_key' => '', 'mxchat_pinecone_host' => '', 'mxchat_pinecone_index' => '', 'mxchat_pinecone_environment' => '' ); //error_log('[MXCHAT-PROMPTS] Failed to unserialize, using defaults'); } } //error_log('[MXCHAT-PROMPTS] Current options from DB: ' . print_r($current_options, true)); // Update the specific field with proper sanitization switch ($field_name) { case 'mxchat_use_pinecone': $new_value = ($value === '1') ? '1' : '0'; break; case 'mxchat_pinecone_api_key': case 'mxchat_pinecone_host': case 'mxchat_pinecone_index': case 'mxchat_pinecone_environment': $new_value = sanitize_text_field($value); if ($field_name === 'mxchat_pinecone_host') { $new_value = str_replace(['https://', 'http://'], '', $new_value); } break; default: wp_send_json_error(['message' => esc_html__('Unknown Pinecone field', 'mxchat')]); } $current_options[$field_name] = $new_value; //error_log('[MXCHAT-PROMPTS] New value for ' . $field_name . ': "' . $new_value . '"'); //error_log('[MXCHAT-PROMPTS] Updated options: ' . print_r($current_options, true)); // Save directly to database to bypass WordPress sanitization $serialized_options = maybe_serialize($current_options); // FIX: Use INSERT ... ON DUPLICATE KEY UPDATE or separate INSERT/UPDATE logic $option_exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s", 'mxchat_pinecone_addon_options' ) ); if ($option_exists > 0) { // Update existing option $save_result = $wpdb->update( $wpdb->options, array('option_value' => $serialized_options), array('option_name' => 'mxchat_pinecone_addon_options'), array('%s'), array('%s') ); //error_log('[MXCHAT-PROMPTS] Updated existing option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED')); } else { // Insert new option $save_result = $wpdb->insert( $wpdb->options, array( 'option_name' => 'mxchat_pinecone_addon_options', 'option_value' => $serialized_options, 'autoload' => 'yes' ), array('%s', '%s', '%s') ); //error_log('[MXCHAT-PROMPTS] Inserted new option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED')); } // Clear any WordPress option cache to ensure get_option() returns fresh data wp_cache_delete('mxchat_pinecone_addon_options', 'options'); // IMPROVED VERIFICATION - Check if the database operation succeeded if ($save_result !== false) { // Double-check by reading fresh from database $verification_raw = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", 'mxchat_pinecone_addon_options' ) ); $verification_options = maybe_unserialize($verification_raw); $verified_value = isset($verification_options[$field_name]) ? $verification_options[$field_name] : 'NOT_FOUND'; //error_log('[MXCHAT-PROMPTS] Final verification - Expected: "' . $new_value . '", Got: "' . $verified_value . '"'); // Use loose comparison (==) instead of strict (===) to avoid type issues if ($verified_value == $new_value || $save_result > 0) { wp_send_json_success(['message' => esc_html__('Pinecone setting saved', 'mxchat')]); } else { // Still return success if the DB operation worked, even if verification is quirky //error_log('[MXCHAT-PROMPTS] Verification mismatch but DB operation succeeded'); wp_send_json_success(['message' => esc_html__('Pinecone setting saved (DB success)', 'mxchat')]); } } else { wp_send_json_error(['message' => esc_html__('Database save failed', 'mxchat')]); } } else { wp_send_json_error(['message' => esc_html__('Invalid field name format', 'mxchat')]); } return; // Exit here for Pinecone settings } // Handle auto-sync settings (existing functionality) if (strpos($name, 'mxchat_auto_sync_') === 0) { $value = ($value === 'on' || $value === '1') ? '1' : '0'; $updated = update_option($name, $value); if ($updated || get_option($name) === $value) { wp_send_json_success(['message' => esc_html__('Auto-sync setting saved', 'mxchat')]); } else { wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]); } } // Handle other prompts options $options = get_option('mxchat_prompts_options', []); $options[$name] = $value; $updated = update_option('mxchat_prompts_options', $options); if ($updated) { wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]); } else { wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]); } } /** * Handles AJAX request for Pinecone settings migration */ public function ajax_migrate_pinecone_settings() { // Verify nonce if (!wp_verify_nonce($_POST['_ajax_nonce'] ?? '', 'mxchat_save_setting_nonce')) { wp_send_json_error('Invalid nonce'); } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error('Unauthorized access'); } // Check if old Pinecone addon options exist $old_options = get_option('mxchat_pinecone_addon_options', array()); if (empty($old_options)) { wp_send_json_success(array('migrated' => false, 'message' => 'No old settings found')); } // Get current core plugin options $current_options = get_option('mxchat_pinecone_addon_options', array()); // Only migrate if core options are empty or if explicitly requested $should_migrate = empty($current_options) || (empty($current_options['mxchat_pinecone_api_key']) && !empty($old_options['mxchat_pinecone_api_key'])); if ($should_migrate) { // Migrate settings with proper sanitization $migrated_options = array( 'mxchat_use_pinecone' => $old_options['mxchat_use_pinecone'] ?? '0', 'mxchat_pinecone_api_key' => sanitize_text_field($old_options['mxchat_pinecone_api_key'] ?? ''), 'mxchat_pinecone_host' => sanitize_text_field($old_options['mxchat_pinecone_host'] ?? ''), 'mxchat_pinecone_index' => sanitize_text_field($old_options['mxchat_pinecone_index'] ?? ''), 'mxchat_pinecone_environment' => sanitize_text_field($old_options['mxchat_pinecone_environment'] ?? '') ); update_option('mxchat_pinecone_addon_options', $migrated_options); wp_send_json_success(array( 'migrated' => true, 'message' => 'Settings migrated successfully from Pinecone add-on' )); } else { wp_send_json_success(array( 'migrated' => false, 'message' => 'Settings already exist in core plugin' )); } } // ======================================== // LICENSE AJAX HANDLERS // ======================================== /** * Validates and activates chat license via AJAX */ public function mxchat_handle_activate_license() { // Check nonce if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) { wp_send_json_error(esc_html__('Invalid security token', 'mxchat')); return; } // Verify user capabilities if (!current_user_can('manage_options')) { wp_send_json_error(esc_html__('Unauthorized access', 'mxchat')); return; } $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : ''; $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : ''; if (empty($license_key) || empty($customer_email)) { wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat')); return; } $product_id = 'MxChatPRO'; $domain = parse_url(home_url(), PHP_URL_HOST); // Get the current domain // Call WooCommerce Software API for activation (not just validation) $response = wp_remote_get( add_query_arg( array( 'wc-api' => 'software-api', 'request' => 'activation', 'email' => $customer_email, 'license_key' => $license_key, 'product_id' => $product_id, 'instance' => $domain, // THIS IS KEY - include the domain as instance 'platform' => 'wordpress' // Optional but good to include ), 'https://mxchat.ai/' ), array( 'timeout' => 60, 'sslverify' => true ) ); if (is_wp_error($response)) { $error_message = $response->get_error_message(); //error_log('MxChat License Activation Error: ' . $error_message); wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $error_message); return; } $response_code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); // Log response for debugging //error_log('MxChat License Response Code: ' . $response_code); //error_log('MxChat License Response Body: ' . $body); if ($response_code !== 200) { wp_send_json_error(esc_html__('Server returned error code: ', 'mxchat') . $response_code); return; } $data = json_decode($body); if ($data && isset($data->activated) && $data->activated) { // Success - save local options update_option('mxchat_license_status', 'active'); update_option('mxchat_pro_email', $customer_email); update_option('mxchat_activation_key', $license_key); delete_option('mxchat_license_error'); // Also track on your website (this is your existing domain tracking) $this->track_domain_on_website($license_key, $customer_email, $domain); wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat'))); } else { $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat'); update_option('mxchat_license_status', 'inactive'); update_option('mxchat_license_error', $error_message); //error_log('MxChat Activation failed: ' . $error_message); wp_send_json_error($error_message); } } /** * Track domain on your website (separate from WooCommerce activation) */ private function track_domain_on_website($license_key, $email, $domain) { // This calls your website's tracking API wp_remote_post('https://mxchat.ai/mxchat-api/activate-license', array( 'body' => array( 'mxchat_pro_email' => $email, 'mxchat_activation_key' => $license_key, 'domain' => $domain ), 'timeout' => 10, 'sslverify' => true )); } /** * Validates license via AJAX with email and key */ public function mxchat_check_license_status() { // Verify nonce if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) { wp_send_json_error('Security check failed'); return; } // Add isset checks for safety $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : ''; $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : ''; // Check if this license is actually active in your system $is_active = (get_option('mxchat_license_status') === 'active' && get_option('mxchat_pro_email') === $email && get_option('mxchat_activation_key') === $key); wp_send_json(array( 'is_active' => $is_active )); } /** * Handle license deactivation - Complete version for plugin */ function mxchat_deactivate_license() { // Add debugging //error_log('MxChat deactivate function called'); // Check nonce if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) { //error_log('MxChat deactivate: Nonce check failed'); wp_send_json_error('Security check failed.'); return; } //error_log('MxChat deactivate: Nonce check passed'); $license_key = get_option('mxchat_activation_key'); $email = get_option('mxchat_pro_email'); $domain = parse_url(home_url(), PHP_URL_HOST); //error_log('MxChat deactivate: License: ' . $license_key . ', Email: ' . $email . ', Domain: ' . $domain); if (empty($license_key) || empty($email)) { //error_log('MxChat deactivate: No active license found'); wp_send_json_error('No active license found.'); return; } // Clear local license data first delete_option('mxchat_license_status'); delete_option('mxchat_pro_email'); delete_option('mxchat_activation_key'); delete_option('mxchat_license_error'); //error_log('MxChat deactivate: Local data cleared'); // Notify your website's API to properly deactivate $response = wp_remote_post('https://mxchat.ai/mxchat-api/deactivate-license', array( 'body' => array( 'license_key' => $license_key, 'email' => $email, 'domain' => $domain ), 'timeout' => 15, 'sslverify' => true )); if (is_wp_error($response)) { //error_log('MxChat deactivate: Server error - ' . $response->get_error_message()); wp_send_json_success(array( 'message' => 'License deactivated locally. Server could not be contacted to free activation slot.', 'server_notified' => false )); return; } $response_body = wp_remote_retrieve_body($response); $response_data = json_decode($response_body, true); //error_log('MxChat deactivate: Server response - ' . $response_body); if (isset($response_data['success']) && $response_data['success']) { //error_log('MxChat deactivate: Success with server notification'); wp_send_json_success(array( 'message' => 'License deactivated successfully. Activation slot has been freed up.', 'server_notified' => true )); } else { //error_log('MxChat deactivate: Server responded but deactivation may have failed'); wp_send_json_success(array( 'message' => 'License deactivated locally. Please check your account dashboard to verify the activation was freed.', 'server_notified' => false )); } } // ======================================== // ACTIONS & INTENTS AJAX HANDLERS // ======================================== /** * Validates nonce and returns JSON error on failure */ public function mxchat_toggle_action() { // Check nonce if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) { wp_send_json_error(array('message' => 'Security check failed')); return; } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Permission denied')); return; } // Validate params $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0; $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false; if (!$intent_id) { wp_send_json_error(array('message' => 'Invalid action ID')); return; } // Update the intent/action status in the database global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; // Using the 'enabled' field - add this field if it doesn't exist $result = $wpdb->update( $table_name, array('enabled' => $enabled ? 1 : 0), array('id' => $intent_id), array('%d'), array('%d') ); if ($result === false) { wp_send_json_error(array('message' => 'Database error')); return; } wp_send_json_success(); } /** * Validates permissions for AJAX request handling */ public function mxchat_update_intent_threshold() { // Check permissions if (!current_user_can('manage_options')) { if (wp_doing_ajax()) { wp_send_json_error(array('message' => 'Unauthorized user')); return; } wp_die(esc_html__('Unauthorized user', 'mxchat')); } // Verify nonce check_admin_referer('mxchat_update_intent_threshold_nonce'); // Process the update if we have valid data if (isset($_POST['intent_id'], $_POST['intent_threshold'])) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; $intent_id = intval($_POST['intent_id']); $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold']))); $similarity_threshold = $threshold_percentage / 100; $result = $wpdb->update( $table_name, ['similarity_threshold' => $similarity_threshold], ['id' => $intent_id], ['%f'], ['%d'] ); // Handle AJAX requests if (wp_doing_ajax()) { if ($result === false) { wp_send_json_error(array('message' => 'Failed to update threshold')); } else { wp_send_json_success(array('threshold' => $threshold_percentage)); } return; } } // Redirect for regular form submissions wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true')); exit; } // ======================================== // HELPER METHODS // ======================================== /** * Returns a specific nonce action string */ private function mxchat_get_nonce_action() { return 'mxchat_license_nonce'; } } // Initialize the AJAX handler new MxChat_Ajax_Handler();