PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.6
MxChat – AI Chatbot & Content Generation for WordPress v3.0.6
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.6, at admin/class-ajax-handler.php

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