PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.4
MxChat – AI Chatbot & Content Generation for WordPress v3.0.4
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / admin / class-ajax-handler.php

class-ajax-handler.php in MxChat – AI Chatbot & Content Generation for WordPress 3.0.4, at admin/class-ajax-handler.php

1,108 lines 46.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * File: admin/class-ajax-handler.php
4 *
5 * Handles all AJAX requests for MxChat admin functionality
6 */
7
8 if (!defined('ABSPATH')) {
9 exit; // Exit if accessed directly
10 }
11
12 class MxChat_Ajax_Handler {
13
14 private $pinecone_manager = null;
15
16 /**
17 * Constructor - Register all AJAX hooks
18 */
19 public function __construct() {
20 $this->mxchat_init_ajax_hooks();
21 }
22
23
24 /**
25 * Register all AJAX action hooks
26 */
27 private function mxchat_init_ajax_hooks() {
28 // Settings AJAX
29 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
30 add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
31 add_action('wp_ajax_migrate_pinecone_settings', array($this, 'ajax_migrate_pinecone_settings'));
32
33 // License AJAX
34 add_action('wp_ajax_mxchat_handle_activate_license', array($this, 'mxchat_handle_activate_license'));
35 add_action('wp_ajax_mxchat_check_license_status', array($this, 'mxchat_check_license_status'));
36 add_action('wp_ajax_mxchat_deactivate_license', array($this, 'mxchat_deactivate_license'));
37
38 // Actions & Intents AJAX
39 add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action'));
40 add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold'));
41
42 add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
43 add_action('wp_ajax_mxchat_check_api_keys', array($this, 'mxchat_check_api_keys'));
44 }
45
46 // ========================================
47 // SETTINGS AJAX HANDLERS
48 // ========================================
49
50 /**
51 * Validates and saves chat settings via AJAX request
52 */
53 public function mxchat_save_setting_callback() {
54 check_ajax_referer('mxchat_save_setting_nonce');
55 if (!current_user_can('manage_options')) {
56 ('MXChat Save: Unauthorized access attempt');
57 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
58 }
59
60 $name = isset($_POST['name']) ? $_POST['name'] : '';
61 // Strip slashes from the value before saving
62 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
63
64 //error_log('MXChat Save: Processing field name: ' . $name);
65 //error_log('MXChat Save: Field value: ' . $value);
66
67 if (empty($name)) {
68 //error_log('MXChat Save: Empty field name detected');
69 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
70 }
71
72 // Load the full options array
73 $options = get_option('mxchat_options', []);
74 //error_log('MXChat Save: Current options array: ' . print_r($options, true));
75
76 // Handle special cases
77 switch ($name) {
78 case 'model':
79 //error_log('MXChat Save: Processing model selection');
80 //error_log('MXChat Save: Model value received: ' . $value);
81 //error_log('MXChat Save: Value type: ' . gettype($value));
82 //error_log('MXChat Save: Value length: ' . strlen($value));
83 //error_log('MXChat Save: Value === "openrouter": ' . ($value === 'openrouter' ? 'YES' : 'NO'));
84
85 // Allow 'openrouter' or validate against whitelist
86 if ($value === 'openrouter') {
87 //error_log('MXChat Save: Setting model to openrouter');
88 $options['model'] = 'openrouter';
89 } else {
90 //error_log('MXChat Save: Checking against whitelist');
91 $allowed_models = array(
92 'gemini-3-pro-preview', 'gemini-3-flash-preview', 'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite',
93 'gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-pro', 'gemini-1.5-flash',
94 'grok-4-0709', 'grok-4-1-fast-reasoning', 'grok-4-1-fast-non-reasoning', 'grok-3-beta', 'grok-3-fast-beta', 'grok-3-mini-beta',
95 'grok-3-mini-fast-beta', 'grok-2',
96 'deepseek-chat',
97 'claude-sonnet-4-5-20250929', 'claude-opus-4-1-20250805', 'claude-haiku-4-5-20251001',
98 'claude-opus-4-20250514', 'claude-sonnet-4-20250514', 'claude-3-7-sonnet-20250219',
99 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229',
100 'claude-3-haiku-20240307',
101 'gpt-5.2', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-4.1-2025-04-14',
102 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo',
103 );
104
105 //error_log('MXChat Save: in_array result: ' . (in_array($value, $allowed_models) ? 'YES' : 'NO'));
106
107 if (in_array($value, $allowed_models)) {
108 //error_log('MXChat Save: Model is in whitelist, saving');
109 $options['model'] = sanitize_text_field($value);
110 } else {
111 //error_log('MXChat Save: Invalid model rejected: ' . $value);
112 //error_log('MXChat Save: Allowed models: ' . print_r($allowed_models, true));
113 wp_send_json_error(['message' => esc_html__('Invalid model selected', 'mxchat')]);
114 return;
115 }
116 }
117 break;
118
119 case 'openrouter_selected_model':
120 //error_log('MXChat Save: Processing OpenRouter model: ' . $value);
121 $options['openrouter_selected_model'] = sanitize_text_field($value);
122 // Force immediate save for new keys
123 //error_log('MXChat Save: OpenRouter model saved immediately');
124 break;
125
126 case 'openrouter_selected_model_name':
127 //error_log('MXChat Save: Processing OpenRouter model name: ' . $value);
128 $options['openrouter_selected_model_name'] = sanitize_text_field($value);
129 // Force immediate save for new keys
130 //error_log('MXChat Save: OpenRouter model name saved immediately');
131 break;
132
133 case 'openrouter_api_key':
134 //error_log('MXChat Save: Processing OpenRouter API key');
135 $options['openrouter_api_key'] = sanitize_text_field($value);
136 break;
137
138 // REMOVED DUPLICATE case 'openrouter_selected_model_name' HERE!
139
140 case 'additional_popular_questions':
141 //error_log('MXChat Save: Processing additional_popular_questions');
142 $questions = json_decode($value, true); // No need for stripslashes here
143 if (is_array($questions)) {
144 $options[$name] = $questions;
145 // Also update old option for backwards compatibility
146 update_option('additional_popular_questions', $questions);
147 //error_log('MXChat Save: Saved ' . count($questions) . ' additional questions');
148 } else {
149 //error_log('MXChat Save: Failed to decode questions JSON');
150 }
151 break;
152 case 'email_blocker_header_content':
153 //error_log('MXChat Save: Processing email_blocker_header_content');
154 // Allow HTML content but sanitize it safely
155 $options[$name] = wp_kses_post($value);
156 break;
157 case 'email_blocker_button_text':
158 //error_log('MXChat Save: Processing email_blocker_button_text');
159 $options[$name] = sanitize_text_field($value);
160 break;
161 case 'name_field_placeholder':
162 //error_log('MXChat Save: Processing name_field_placeholder');
163 $options[$name] = sanitize_text_field($value);
164 break;
165 case 'similarity_threshold':
166 //error_log('MXChat Save: Processing similarity_threshold');
167 // Save to the options array
168 $options[$name] = $value;
169 break;
170 case 'user_message_bg_color':
171 case 'user_message_font_color':
172 case 'bot_message_bg_color':
173 case 'bot_message_font_color':
174 case 'top_bar_bg_color':
175 case 'send_button_font_color':
176 case 'chatbot_background_color':
177 case 'icon_color':
178 case 'chat_input_font_color':
179 case 'live_agent_message_bg_color':
180 case 'live_agent_message_font_color':
181 case 'mode_indicator_bg_color':
182 case 'mode_indicator_font_color':
183 case 'toolbar_icon_color':
184 case 'quick_questions_toggle_color':
185 //error_log('MXChat Save: Processing color value: ' . $name);
186 // Store color values directly
187 $options[$name] = $value;
188 break;
189 case 'live_agent_status':
190 //error_log('MXChat Save: Processing live_agent_status');
191 // Set the new value
192 $options[$name] = ($value === 'on') ? 'on' : 'off';
193 break;
194 case 'enable_web_search':
195 //error_log('MXChat Save: Processing enable_web_search');
196 $options[$name] = ($value === 'on') ? 'on' : 'off';
197 break;
198 case 'enable_woocommerce_integration':
199 //error_log('MXChat Save: Processing enable_woocommerce_integration');
200 // Handle values that used to be 1/0
201 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
202 break;
203 case 'post_type_visibility_mode':
204 // Validate mode value
205 $allowed_modes = array('all', 'include', 'exclude');
206 $options[$name] = in_array($value, $allowed_modes) ? $value : 'all';
207 break;
208 case 'post_type_visibility_list':
209 // Handle JSON array of post types
210 $post_types = json_decode($value, true);
211 if (is_array($post_types)) {
212 // Sanitize each post type slug
213 $options[$name] = array_map('sanitize_key', $post_types);
214 } else {
215 $options[$name] = array();
216 }
217 break;
218 default:
219 // Handle transcripts options
220 if (strpos($name, 'mxchat_transcripts_options') !== false) {
221 // Extract field name from mxchat_transcripts_options[field_name]
222 if (preg_match('/mxchat_transcripts_options\[([^\]]+)\]/', $name, $matches)) {
223 $field_name = $matches[1];
224
225 // Get current transcripts options
226 $transcripts_options = get_option('mxchat_transcripts_options', array());
227
228 // Ensure it's an array
229 if (!is_array($transcripts_options)) {
230 $transcripts_options = array();
231 }
232
233 // Handle checkbox values (convert 'on'/'off' to 1/0)
234 if ($value === 'on' || $value === '1') {
235 $transcripts_options[$field_name] = 1;
236 } else if ($value === 'off' || $value === '0' || $value === '') {
237 $transcripts_options[$field_name] = 0;
238 } else {
239 // For text/select fields, sanitize appropriately
240 if ($field_name === 'mxchat_notification_email') {
241 $transcripts_options[$field_name] = sanitize_email($value);
242 } else {
243 $transcripts_options[$field_name] = sanitize_text_field($value);
244 }
245 }
246
247 // Use direct database update to bypass any filters
248 global $wpdb;
249
250 // Serialize the options array
251 $serialized = maybe_serialize($transcripts_options);
252
253 // Direct database update
254 $result = $wpdb->update(
255 $wpdb->options,
256 array('option_value' => $serialized),
257 array('option_name' => 'mxchat_transcripts_options'),
258 array('%s'),
259 array('%s')
260 );
261
262 // Clear all caches after direct DB update
263 wp_cache_delete('mxchat_transcripts_options', 'options');
264 wp_cache_delete('alloptions', 'options');
265 wp_cache_flush();
266
267 // Verify the save worked by reading directly from database
268 $db_value = $wpdb->get_var("SELECT option_value FROM {$wpdb->options} WHERE option_name = 'mxchat_transcripts_options'");
269
270 // Log for debugging
271 error_log('MXChat Transcripts Save: field=' . $field_name . ', value=' . $value . ', db_result=' . var_export($result, true));
272 error_log('MXChat Transcripts Save: saved options=' . print_r($transcripts_options, true));
273 error_log('MXChat Transcripts Verify: DB direct after save=' . $db_value);
274
275 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
276 return;
277 }
278 }
279 // First check for rate limits settings
280 else if (strpos($name, 'mxchat_options[rate_limits]') !== false) {
281 //error_log('MXChat Save: Detected rate_limits field: ' . $name);
282
283 // Extract role ID and setting from the name
284 preg_match('/\[rate_limits\]\[(.*?)\]\[(.*?)\]/', $name, $matches);
285 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
286
287 if (isset($matches[1]) && isset($matches[2])) {
288 $role_id = $matches[1];
289 $setting_key = $matches[2]; // limit, timeframe, or message
290
291 //error_log('MXChat Save: Role ID = ' . $role_id . ', Setting Key = ' . $setting_key);
292
293 // Initialize rate_limits if it doesn't exist
294 if (!isset($options['rate_limits'])) {
295 // //error_log('MXChat Save: Initializing rate_limits array');
296 $options['rate_limits'] = [];
297 }
298
299 // Initialize role settings if it doesn't exist
300 if (!isset($options['rate_limits'][$role_id])) {
301 //error_log('MXChat Save: Initializing rate_limits for role: ' . $role_id);
302 $options['rate_limits'][$role_id] = [
303 'limit' => ($role_id === 'logged_out') ? '10' : '100',
304 'timeframe' => 'daily',
305 'message' => 'Rate limit exceeded. Please try again later.'
306 ];
307 }
308
309 // Update the specific setting
310 $options['rate_limits'][$role_id][$setting_key] = $value;
311 //error_log('MXChat Save: Updated rate_limits[' . $role_id . '][' . $setting_key . '] = ' . $value);
312 } else {
313 //error_log('MXChat Save: Failed to parse rate_limits pattern: ' . $name);
314 }
315 }
316 // Then check for role rate limits (old format)
317 else if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
318 //error_log('MXChat Save: Processing role_rate_limits field: ' . $name);
319 // Extract role ID from the name
320 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
321 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
322
323 if (isset($matches[1])) {
324 $role_id = $matches[1];
325 // Initialize role_rate_limits if it doesn't exist
326 if (!isset($options['role_rate_limits'])) {
327 //error_log('MXChat Save: Initializing role_rate_limits array');
328 $options['role_rate_limits'] = [];
329 }
330 // Update the specific role's rate limit
331 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
332 //error_log('MXChat Save: Updated role_rate_limits[' . $role_id . '] = ' . $value);
333 } else {
334 //error_log('MXChat Save: Failed to parse role_rate_limits pattern: ' . $name);
335 }
336 }
337 // Handle toggles
338 else if (strpos($name, 'toggle') !== false || in_array($name, [
339 'chat_persistence_toggle',
340 'privacy_toggle',
341 'complianz_toggle',
342 'chat_toolbar_toggle',
343 'show_pdf_upload_button',
344 'show_word_upload_button',
345 'enable_streaming_toggle',
346 'contextual_awareness_toggle',
347 'citation_links_toggle',
348 'enable_email_block',
349 'enable_name_field',
350 'show_frontend_debugger'
351 ])) {
352 //error_log('MXChat Save: Processing toggle: ' . $name);
353 $options[$name] = ($value === 'on') ? 'on' : 'off';
354 } else {
355 //error_log('MXChat Save: Processing standard field: ' . $name);
356 // Store all other values directly
357 $options[$name] = $value;
358 }
359 break;
360 }
361
362 // Save all updates to the options array
363 $updated = update_option('mxchat_options', $options);
364 //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
365 //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
366
367 // Always return success even if WordPress says nothing changed
368 // (which happens when the value is the same as before)
369 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
370 }
371
372 /**
373 * Save the selected bot for knowledge base operations
374 */
375 public function mxchat_save_selected_bot() {
376 // Check nonce
377 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) {
378 wp_send_json_error('Invalid nonce');
379 }
380
381 // Check permissions
382 if (!current_user_can('manage_options')) {
383 wp_send_json_error('Unauthorized');
384 }
385
386 $bot_id = isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default';
387
388 // Save as user meta for the current user
389 $user_id = get_current_user_id();
390 update_user_meta($user_id, 'mxchat_selected_knowledge_bot', $bot_id);
391
392 // Also save as an option for site-wide default
393 update_option('mxchat_current_knowledge_bot', $bot_id);
394
395 // No cache clearing needed since we removed caching
396
397 wp_send_json_success(array(
398 'message' => 'Bot selection saved',
399 'bot_id' => $bot_id
400 ));
401 }
402
403 /**
404 * Handles AJAX request for saving chat settings
405 */
406 public function mxchat_save_prompts_setting_callback() {
407 check_ajax_referer('mxchat_prompts_setting_nonce');
408
409 if (!current_user_can('manage_options')) {
410 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
411 }
412
413 $name = isset($_POST['name']) ? $_POST['name'] : '';
414 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
415
416 //error_log('[MXCHAT-PROMPTS] Saving setting: ' . $name . ' = ' . $value);
417
418 if (empty($name)) {
419 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
420 }
421
422 // Handle Pinecone settings - BYPASS WORDPRESS SANITIZATION
423 if (strpos($name, 'mxchat_pinecone_addon_options') !== false) {
424 //error_log('[MXCHAT-PROMPTS] Processing Pinecone setting: ' . $name);
425
426 // Extract the field name
427 if (preg_match('/mxchat_pinecone_addon_options\[([^\]]+)\]/', $name, $matches)) {
428 $field_name = $matches[1];
429 //error_log('[MXCHAT-PROMPTS] Extracted field name: ' . $field_name);
430
431 // Get current options directly from database - NO WordPress filters
432 global $wpdb;
433 $current_options_raw = $wpdb->get_var(
434 $wpdb->prepare(
435 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
436 'mxchat_pinecone_addon_options'
437 )
438 );
439
440 // FIX: Handle the case where the option doesn't exist yet
441 if ($current_options_raw === null) {
442 // Option doesn't exist, create it with default values
443 $current_options = array(
444 'mxchat_use_pinecone' => '0',
445 'mxchat_pinecone_api_key' => '',
446 'mxchat_pinecone_host' => '',
447 'mxchat_pinecone_index' => '',
448 'mxchat_pinecone_environment' => ''
449 );
450 //error_log('[MXCHAT-PROMPTS] Option does not exist, creating with defaults');
451 } else {
452 // Unserialize the raw data
453 $current_options = maybe_unserialize($current_options_raw);
454 if (!is_array($current_options)) {
455 // Fallback to defaults if unserialization fails
456 $current_options = array(
457 'mxchat_use_pinecone' => '0',
458 'mxchat_pinecone_api_key' => '',
459 'mxchat_pinecone_host' => '',
460 'mxchat_pinecone_index' => '',
461 'mxchat_pinecone_environment' => ''
462 );
463 //error_log('[MXCHAT-PROMPTS] Failed to unserialize, using defaults');
464 }
465 }
466
467 //error_log('[MXCHAT-PROMPTS] Current options from DB: ' . print_r($current_options, true));
468
469 // Update the specific field with proper sanitization
470 switch ($field_name) {
471 case 'mxchat_use_pinecone':
472 $new_value = ($value === '1') ? '1' : '0';
473 break;
474 case 'mxchat_pinecone_api_key':
475 case 'mxchat_pinecone_host':
476 case 'mxchat_pinecone_index':
477 case 'mxchat_pinecone_environment':
478 $new_value = sanitize_text_field($value);
479 if ($field_name === 'mxchat_pinecone_host') {
480 $new_value = str_replace(['https://', 'http://'], '', $new_value);
481 }
482 break;
483 default:
484 wp_send_json_error(['message' => esc_html__('Unknown Pinecone field', 'mxchat')]);
485 }
486
487 $current_options[$field_name] = $new_value;
488 //error_log('[MXCHAT-PROMPTS] New value for ' . $field_name . ': "' . $new_value . '"');
489 //error_log('[MXCHAT-PROMPTS] Updated options: ' . print_r($current_options, true));
490
491 // Save directly to database to bypass WordPress sanitization
492 $serialized_options = maybe_serialize($current_options);
493
494 // FIX: Use INSERT ... ON DUPLICATE KEY UPDATE or separate INSERT/UPDATE logic
495 $option_exists = $wpdb->get_var(
496 $wpdb->prepare(
497 "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s",
498 'mxchat_pinecone_addon_options'
499 )
500 );
501
502 if ($option_exists > 0) {
503 // Update existing option
504 $save_result = $wpdb->update(
505 $wpdb->options,
506 array('option_value' => $serialized_options),
507 array('option_name' => 'mxchat_pinecone_addon_options'),
508 array('%s'),
509 array('%s')
510 );
511 //error_log('[MXCHAT-PROMPTS] Updated existing option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
512 } else {
513 // Insert new option
514 $save_result = $wpdb->insert(
515 $wpdb->options,
516 array(
517 'option_name' => 'mxchat_pinecone_addon_options',
518 'option_value' => $serialized_options,
519 'autoload' => 'yes'
520 ),
521 array('%s', '%s', '%s')
522 );
523 //error_log('[MXCHAT-PROMPTS] Inserted new option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
524 }
525
526 // Clear any WordPress option cache to ensure get_option() returns fresh data
527 wp_cache_delete('mxchat_pinecone_addon_options', 'options');
528
529 // IMPROVED VERIFICATION - Check if the database operation succeeded
530 if ($save_result !== false) {
531 // Double-check by reading fresh from database
532 $verification_raw = $wpdb->get_var(
533 $wpdb->prepare(
534 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
535 'mxchat_pinecone_addon_options'
536 )
537 );
538 $verification_options = maybe_unserialize($verification_raw);
539 $verified_value = isset($verification_options[$field_name]) ? $verification_options[$field_name] : 'NOT_FOUND';
540
541 //error_log('[MXCHAT-PROMPTS] Final verification - Expected: "' . $new_value . '", Got: "' . $verified_value . '"');
542
543 // Use loose comparison (==) instead of strict (===) to avoid type issues
544 if ($verified_value == $new_value || $save_result > 0) {
545 wp_send_json_success(['message' => esc_html__('Pinecone setting saved', 'mxchat')]);
546 } else {
547 // Still return success if the DB operation worked, even if verification is quirky
548 //error_log('[MXCHAT-PROMPTS] Verification mismatch but DB operation succeeded');
549 wp_send_json_success(['message' => esc_html__('Pinecone setting saved (DB success)', 'mxchat')]);
550 }
551 } else {
552 wp_send_json_error(['message' => esc_html__('Database save failed', 'mxchat')]);
553 }
554 } else {
555 wp_send_json_error(['message' => esc_html__('Invalid field name format', 'mxchat')]);
556 }
557
558 return; // Exit here for Pinecone settings
559 }
560 // Handle auto-sync settings (existing functionality)
561 if (strpos($name, 'mxchat_auto_sync_') === 0) {
562 $value = ($value === 'on' || $value === '1') ? '1' : '0';
563 $updated = update_option($name, $value);
564
565 if ($updated || get_option($name) === $value) {
566 wp_send_json_success(['message' => esc_html__('Auto-sync setting saved', 'mxchat')]);
567 } else {
568 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
569 }
570 }
571
572 // Handle chunking settings - use direct DB access to bypass WordPress filters
573 if (strpos($name, 'mxchat_chunk') === 0 || $name === 'mxchat_chunking_enabled') {
574 global $wpdb;
575
576 // Get current options directly from database
577 $current_options_raw = $wpdb->get_var(
578 $wpdb->prepare(
579 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
580 'mxchat_options'
581 )
582 );
583
584 $options = $current_options_raw !== null ? maybe_unserialize($current_options_raw) : array();
585 if (!is_array($options)) {
586 $options = array();
587 }
588
589 // Update the specific chunking field
590 if ($name === 'mxchat_chunking_enabled') {
591 $options['chunking_enabled'] = in_array($value, array('on', '1', 'true', true), true);
592 } elseif ($name === 'mxchat_chunk_size') {
593 $options['chunk_size'] = max(1000, min(10000, intval($value)));
594 }
595
596 // Save directly to database
597 $serialized_options = maybe_serialize($options);
598
599 $option_exists = $wpdb->get_var(
600 $wpdb->prepare(
601 "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s",
602 'mxchat_options'
603 )
604 );
605
606 if ($option_exists > 0) {
607 $save_result = $wpdb->update(
608 $wpdb->options,
609 array('option_value' => $serialized_options),
610 array('option_name' => 'mxchat_options'),
611 array('%s'),
612 array('%s')
613 );
614 } else {
615 $save_result = $wpdb->insert(
616 $wpdb->options,
617 array(
618 'option_name' => 'mxchat_options',
619 'option_value' => $serialized_options,
620 'autoload' => 'yes'
621 ),
622 array('%s', '%s', '%s')
623 );
624 }
625
626 // Clear object cache for this option
627 wp_cache_delete('mxchat_options', 'options');
628
629 if ($save_result !== false) {
630 wp_send_json_success(['message' => esc_html__('Chunking setting saved', 'mxchat')]);
631 } else {
632 wp_send_json_error(['message' => esc_html__('Failed to save chunking setting', 'mxchat')]);
633 }
634 return;
635 }
636
637 // Handle ACF field exclusion toggles
638 if (strpos($name, 'mxchat_acf_field_') === 0) {
639 // Extract field name from the input name (e.g., mxchat_acf_field_private_notes -> private_notes)
640 $field_name = str_replace('mxchat_acf_field_', '', $name);
641 $is_enabled = ($value === 'on' || $value === '1');
642
643 // Get current excluded fields
644 $excluded_fields = get_option('mxchat_acf_excluded_fields', array());
645 if (!is_array($excluded_fields)) {
646 $excluded_fields = array();
647 }
648
649 if ($is_enabled) {
650 // Remove from exclusion list (field should be included)
651 $excluded_fields = array_values(array_diff($excluded_fields, array($field_name)));
652 } else {
653 // Add to exclusion list (field should be excluded)
654 if (!in_array($field_name, $excluded_fields)) {
655 $excluded_fields[] = $field_name;
656 }
657 }
658
659 $updated = update_option('mxchat_acf_excluded_fields', $excluded_fields);
660
661 if ($updated || true) { // Always report success since the state may already be correct
662 wp_send_json_success([
663 'message' => $is_enabled
664 ? sprintf(esc_html__('Field "%s" will be included in imports', 'mxchat'), $field_name)
665 : sprintf(esc_html__('Field "%s" will be excluded from imports', 'mxchat'), $field_name)
666 ]);
667 } else {
668 wp_send_json_error(['message' => esc_html__('Failed to save ACF field setting', 'mxchat')]);
669 }
670 return;
671 }
672
673 // Handle custom post meta whitelist
674 if ($name === 'mxchat_custom_meta_whitelist') {
675 $updated = update_option('mxchat_custom_meta_whitelist', sanitize_textarea_field($value));
676
677 if ($updated || true) { // Always report success since the state may already be correct
678 wp_send_json_success([
679 'message' => esc_html__('Custom meta whitelist saved', 'mxchat')
680 ]);
681 } else {
682 wp_send_json_error(['message' => esc_html__('Failed to save custom meta whitelist', 'mxchat')]);
683 }
684 return;
685 }
686
687 // Handle other prompts options
688 $options = get_option('mxchat_prompts_options', []);
689 $options[$name] = $value;
690 $updated = update_option('mxchat_prompts_options', $options);
691
692 if ($updated) {
693 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
694 } else {
695 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
696 }
697 }
698
699
700 /**
701 * Handles AJAX request for Pinecone settings migration
702 */
703 public function ajax_migrate_pinecone_settings() {
704 // Verify nonce
705 if (!wp_verify_nonce($_POST['_ajax_nonce'] ?? '', 'mxchat_save_setting_nonce')) {
706 wp_send_json_error('Invalid nonce');
707 }
708
709 // Check permissions
710 if (!current_user_can('manage_options')) {
711 wp_send_json_error('Unauthorized access');
712 }
713
714 // Check if old Pinecone addon options exist
715 $old_options = get_option('mxchat_pinecone_addon_options', array());
716
717 if (empty($old_options)) {
718 wp_send_json_success(array('migrated' => false, 'message' => 'No old settings found'));
719 }
720
721 // Get current core plugin options
722 $current_options = get_option('mxchat_pinecone_addon_options', array());
723
724 // Only migrate if core options are empty or if explicitly requested
725 $should_migrate = empty($current_options) ||
726 (empty($current_options['mxchat_pinecone_api_key']) && !empty($old_options['mxchat_pinecone_api_key']));
727
728 if ($should_migrate) {
729 // Migrate settings with proper sanitization
730 $migrated_options = array(
731 'mxchat_use_pinecone' => $old_options['mxchat_use_pinecone'] ?? '0',
732 'mxchat_pinecone_api_key' => sanitize_text_field($old_options['mxchat_pinecone_api_key'] ?? ''),
733 'mxchat_pinecone_host' => sanitize_text_field($old_options['mxchat_pinecone_host'] ?? ''),
734 'mxchat_pinecone_index' => sanitize_text_field($old_options['mxchat_pinecone_index'] ?? ''),
735 'mxchat_pinecone_environment' => sanitize_text_field($old_options['mxchat_pinecone_environment'] ?? '')
736 );
737
738 update_option('mxchat_pinecone_addon_options', $migrated_options);
739
740 wp_send_json_success(array(
741 'migrated' => true,
742 'message' => 'Settings migrated successfully from Pinecone add-on'
743 ));
744 } else {
745 wp_send_json_success(array(
746 'migrated' => false,
747 'message' => 'Settings already exist in core plugin'
748 ));
749 }
750 }
751
752
753 // ========================================
754 // LICENSE AJAX HANDLERS
755 // ========================================
756
757 /**
758 * Validates and activates chat license via AJAX
759 */
760 public function mxchat_handle_activate_license() {
761 // Check nonce
762 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
763 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
764 return;
765 }
766
767 // Verify user capabilities
768 if (!current_user_can('manage_options')) {
769 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
770 return;
771 }
772
773 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
774 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
775
776 if (empty($license_key) || empty($customer_email)) {
777 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
778 return;
779 }
780
781 $product_id = 'MxChatPRO';
782 $domain = parse_url(home_url(), PHP_URL_HOST); // Get the current domain
783
784 // Call WooCommerce Software API for activation (not just validation)
785 $response = wp_remote_get(
786 add_query_arg(
787 array(
788 'wc-api' => 'software-api',
789 'request' => 'activation',
790 'email' => $customer_email,
791 'license_key' => $license_key,
792 'product_id' => $product_id,
793 'instance' => $domain, // THIS IS KEY - include the domain as instance
794 'platform' => 'wordpress' // Optional but good to include
795 ),
796 'https://mxchat.ai/'
797 ),
798 array(
799 'timeout' => 60,
800 'sslverify' => true
801 )
802 );
803
804 if (is_wp_error($response)) {
805 $error_message = $response->get_error_message();
806 //error_log('MxChat License Activation Error: ' . $error_message);
807 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $error_message);
808 return;
809 }
810
811 $response_code = wp_remote_retrieve_response_code($response);
812 $body = wp_remote_retrieve_body($response);
813
814 // Log response for debugging
815 //error_log('MxChat License Response Code: ' . $response_code);
816 //error_log('MxChat License Response Body: ' . $body);
817
818 if ($response_code !== 200) {
819 wp_send_json_error(esc_html__('Server returned error code: ', 'mxchat') . $response_code);
820 return;
821 }
822
823 $data = json_decode($body);
824
825 if ($data && isset($data->activated) && $data->activated) {
826 // Success - save local options
827 update_option('mxchat_license_status', 'active');
828 update_option('mxchat_pro_email', $customer_email);
829 update_option('mxchat_activation_key', $license_key);
830 delete_option('mxchat_license_error');
831
832 // Also track on your website (this is your existing domain tracking)
833 $this->track_domain_on_website($license_key, $customer_email, $domain);
834
835 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
836 } else {
837 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
838 update_option('mxchat_license_status', 'inactive');
839 update_option('mxchat_license_error', $error_message);
840
841 //error_log('MxChat Activation failed: ' . $error_message);
842 wp_send_json_error($error_message);
843 }
844 }
845
846 /**
847 * Track domain on your website (separate from WooCommerce activation)
848 */
849 private function track_domain_on_website($license_key, $email, $domain) {
850 // This calls your website's tracking API
851 wp_remote_post('https://mxchat.ai/mxchat-api/activate-license', array(
852 'body' => array(
853 'mxchat_pro_email' => $email,
854 'mxchat_activation_key' => $license_key,
855 'domain' => $domain
856 ),
857 'timeout' => 10,
858 'sslverify' => true
859 ));
860 }
861
862 /**
863 * Validates license via AJAX with email and key
864 */
865 public function mxchat_check_license_status() {
866 // Verify nonce
867 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
868 wp_send_json_error('Security check failed');
869 return;
870 }
871
872 // Add isset checks for safety
873 $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
874 $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : '';
875
876 // Check if this license is actually active in your system
877 $is_active = (get_option('mxchat_license_status') === 'active' &&
878 get_option('mxchat_pro_email') === $email &&
879 get_option('mxchat_activation_key') === $key);
880
881 wp_send_json(array(
882 'is_active' => $is_active
883 ));
884 }
885
886 /**
887 * Handle license deactivation - Complete version for plugin
888 */
889 function mxchat_deactivate_license() {
890 // Add debugging
891 //error_log('MxChat deactivate function called');
892
893 // Check nonce
894 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
895 //error_log('MxChat deactivate: Nonce check failed');
896 wp_send_json_error('Security check failed.');
897 return;
898 }
899
900 //error_log('MxChat deactivate: Nonce check passed');
901
902 $license_key = get_option('mxchat_activation_key');
903 $email = get_option('mxchat_pro_email');
904 $domain = parse_url(home_url(), PHP_URL_HOST);
905
906 //error_log('MxChat deactivate: License: ' . $license_key . ', Email: ' . $email . ', Domain: ' . $domain);
907
908 if (empty($license_key) || empty($email)) {
909 //error_log('MxChat deactivate: No active license found');
910 wp_send_json_error('No active license found.');
911 return;
912 }
913
914 // Clear local license data first
915 delete_option('mxchat_license_status');
916 delete_option('mxchat_pro_email');
917 delete_option('mxchat_activation_key');
918 delete_option('mxchat_license_error');
919
920 //error_log('MxChat deactivate: Local data cleared');
921
922 // Notify your website's API to properly deactivate
923 $response = wp_remote_post('https://mxchat.ai/mxchat-api/deactivate-license', array(
924 'body' => array(
925 'license_key' => $license_key,
926 'email' => $email,
927 'domain' => $domain
928 ),
929 'timeout' => 15,
930 'sslverify' => true
931 ));
932
933 if (is_wp_error($response)) {
934 //error_log('MxChat deactivate: Server error - ' . $response->get_error_message());
935 wp_send_json_success(array(
936 'message' => 'License deactivated locally. Server could not be contacted to free activation slot.',
937 'server_notified' => false
938 ));
939 return;
940 }
941
942 $response_body = wp_remote_retrieve_body($response);
943 $response_data = json_decode($response_body, true);
944
945 //error_log('MxChat deactivate: Server response - ' . $response_body);
946
947 if (isset($response_data['success']) && $response_data['success']) {
948 //error_log('MxChat deactivate: Success with server notification');
949 wp_send_json_success(array(
950 'message' => 'License deactivated successfully. Activation slot has been freed up.',
951 'server_notified' => true
952 ));
953 } else {
954 //error_log('MxChat deactivate: Server responded but deactivation may have failed');
955 wp_send_json_success(array(
956 'message' => 'License deactivated locally. Please check your account dashboard to verify the activation was freed.',
957 'server_notified' => false
958 ));
959 }
960 }
961
962
963 // ========================================
964 // ACTIONS & INTENTS AJAX HANDLERS
965 // ========================================
966
967 /**
968 * Validates nonce and returns JSON error on failure
969 */
970 public function mxchat_toggle_action() {
971 // Check nonce
972 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
973 wp_send_json_error(array('message' => 'Security check failed'));
974 return;
975 }
976
977 // Check permissions
978 if (!current_user_can('manage_options')) {
979 wp_send_json_error(array('message' => 'Permission denied'));
980 return;
981 }
982
983 // Validate params
984 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
985 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
986
987 if (!$intent_id) {
988 wp_send_json_error(array('message' => 'Invalid action ID'));
989 return;
990 }
991
992 // Update the intent/action status in the database
993 global $wpdb;
994 $table_name = $wpdb->prefix . 'mxchat_intents';
995
996 // Using the 'enabled' field - add this field if it doesn't exist
997 $result = $wpdb->update(
998 $table_name,
999 array('enabled' => $enabled ? 1 : 0),
1000 array('id' => $intent_id),
1001 array('%d'),
1002 array('%d')
1003 );
1004
1005 if ($result === false) {
1006 wp_send_json_error(array('message' => 'Database error'));
1007 return;
1008 }
1009
1010 wp_send_json_success();
1011 }
1012
1013
1014 /**
1015 * Validates permissions for AJAX request handling
1016 */
1017 public function mxchat_update_intent_threshold() {
1018 // Check permissions
1019 if (!current_user_can('manage_options')) {
1020 if (wp_doing_ajax()) {
1021 wp_send_json_error(array('message' => 'Unauthorized user'));
1022 return;
1023 }
1024 wp_die(esc_html__('Unauthorized user', 'mxchat'));
1025 }
1026
1027 // Verify nonce
1028 check_admin_referer('mxchat_update_intent_threshold_nonce');
1029
1030 // Process the update if we have valid data
1031 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
1032 global $wpdb;
1033 $table_name = $wpdb->prefix . 'mxchat_intents';
1034 $intent_id = intval($_POST['intent_id']);
1035 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
1036 $similarity_threshold = $threshold_percentage / 100;
1037
1038 $result = $wpdb->update(
1039 $table_name,
1040 ['similarity_threshold' => $similarity_threshold],
1041 ['id' => $intent_id],
1042 ['%f'],
1043 ['%d']
1044 );
1045
1046 // Handle AJAX requests
1047 if (wp_doing_ajax()) {
1048 if ($result === false) {
1049 wp_send_json_error(array('message' => 'Failed to update threshold'));
1050 } else {
1051 wp_send_json_success(array('threshold' => $threshold_percentage));
1052 }
1053 return;
1054 }
1055 }
1056
1057 // Redirect for regular form submissions
1058 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
1059 exit;
1060 }
1061
1062 // ========================================
1063 // HELPER METHODS
1064 // ========================================
1065
1066 /**
1067 * Returns a specific nonce action string
1068 */
1069 private function mxchat_get_nonce_action() {
1070 return 'mxchat_license_nonce';
1071 }
1072
1073 /**
1074 * Check API key status for all providers
1075 */
1076 public function mxchat_check_api_keys() {
1077 // Check nonce
1078 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) {
1079 wp_send_json_error('Invalid nonce');
1080 }
1081
1082 // Check permissions
1083 if (!current_user_can('manage_options')) {
1084 wp_send_json_error('Unauthorized');
1085 }
1086
1087 // Get current options
1088 $options = get_option('mxchat_options', array());
1089
1090 // Check which API keys are present
1091 $api_key_status = array(
1092 'openai' => !empty($options['api_key']),
1093 'claude' => !empty($options['claude_api_key']),
1094 'xai' => !empty($options['xai_api_key']),
1095 'deepseek' => !empty($options['deepseek_api_key']),
1096 'gemini' => !empty($options['gemini_api_key']),
1097 'openrouter' => !empty($options['openrouter_api_key']),
1098 'voyage' => !empty($options['voyage_api_key'])
1099 );
1100
1101 wp_send_json_success($api_key_status);
1102 }
1103
1104 }
1105
1106 // Initialize the AJAX handler
1107 new MxChat_Ajax_Handler();
1108