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

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