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

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