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

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