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

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