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

926 lines 38.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * File: admin/class-ajax-handler.php
4 *
5 * Handles all AJAX requests for MxChat admin functionality
6 */
7
8 if (!defined('ABSPATH')) {
9 exit; // Exit if accessed directly
10 }
11
12 class MxChat_Ajax_Handler {
13
14 private $pinecone_manager = null;
15
16 /**
17 * Constructor - Register all AJAX hooks
18 */
19 public function __construct() {
20 $this->mxchat_init_ajax_hooks();
21 }
22
23
24 /**
25 * Register all AJAX action hooks
26 */
27 private function mxchat_init_ajax_hooks() {
28 // Settings AJAX
29 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
30 add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
31 add_action('wp_ajax_migrate_pinecone_settings', array($this, 'ajax_migrate_pinecone_settings'));
32
33 // License AJAX
34 add_action('wp_ajax_mxchat_handle_activate_license', array($this, 'mxchat_handle_activate_license'));
35 add_action('wp_ajax_mxchat_check_license_status', array($this, 'mxchat_check_license_status'));
36 add_action('wp_ajax_mxchat_deactivate_license', array($this, 'mxchat_deactivate_license'));
37
38 // Actions & Intents AJAX
39 add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action'));
40 add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold'));
41
42 add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
43 add_action('wp_ajax_mxchat_check_api_keys', array($this, 'mxchat_check_api_keys'));
44 }
45
46 // ========================================
47 // SETTINGS AJAX HANDLERS
48 // ========================================
49
50 /**
51 * Validates and saves chat settings via AJAX request
52 */
53 public function mxchat_save_setting_callback() {
54 check_ajax_referer('mxchat_save_setting_nonce');
55 if (!current_user_can('manage_options')) {
56 ('MXChat Save: Unauthorized access attempt');
57 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
58 }
59
60 $name = isset($_POST['name']) ? $_POST['name'] : '';
61 // Strip slashes from the value before saving
62 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
63
64 //error_log('MXChat Save: Processing field name: ' . $name);
65 //error_log('MXChat Save: Field value: ' . $value);
66
67 if (empty($name)) {
68 //error_log('MXChat Save: Empty field name detected');
69 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
70 }
71
72 // Load the full options array
73 $options = get_option('mxchat_options', []);
74 //error_log('MXChat Save: Current options array: ' . print_r($options, true));
75
76 // Handle special cases
77 switch ($name) {
78 case 'model':
79 //error_log('MXChat Save: Processing model selection');
80 //error_log('MXChat Save: Model value received: ' . $value);
81 //error_log('MXChat Save: Value type: ' . gettype($value));
82 //error_log('MXChat Save: Value length: ' . strlen($value));
83 //error_log('MXChat Save: Value === "openrouter": ' . ($value === 'openrouter' ? 'YES' : 'NO'));
84
85 // Allow 'openrouter' or validate against whitelist
86 if ($value === 'openrouter') {
87 //error_log('MXChat Save: Setting model to openrouter');
88 $options['model'] = 'openrouter';
89 } else {
90 //error_log('MXChat Save: Checking against whitelist');
91 $allowed_models = array(
92 'gemini-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 ])) {
285 //error_log('MXChat Save: Processing toggle: ' . $name);
286 $options[$name] = ($value === 'on') ? 'on' : 'off';
287 } else {
288 //error_log('MXChat Save: Processing standard field: ' . $name);
289 // Store all other values directly
290 $options[$name] = $value;
291 }
292 break;
293 }
294
295 // Save all updates to the options array
296 $updated = update_option('mxchat_options', $options);
297 //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
298 //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
299
300 // Always return success even if WordPress says nothing changed
301 // (which happens when the value is the same as before)
302 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
303 }
304
305 /**
306 * Save the selected bot for knowledge base operations
307 */
308 public function mxchat_save_selected_bot() {
309 // Check nonce
310 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) {
311 wp_send_json_error('Invalid nonce');
312 }
313
314 // Check permissions
315 if (!current_user_can('manage_options')) {
316 wp_send_json_error('Unauthorized');
317 }
318
319 $bot_id = isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default';
320
321 // Save as user meta for the current user
322 $user_id = get_current_user_id();
323 update_user_meta($user_id, 'mxchat_selected_knowledge_bot', $bot_id);
324
325 // Also save as an option for site-wide default
326 update_option('mxchat_current_knowledge_bot', $bot_id);
327
328 // No cache clearing needed since we removed caching
329
330 wp_send_json_success(array(
331 'message' => 'Bot selection saved',
332 'bot_id' => $bot_id
333 ));
334 }
335
336 /**
337 * Handles AJAX request for saving chat settings
338 */
339 public function mxchat_save_prompts_setting_callback() {
340 check_ajax_referer('mxchat_prompts_setting_nonce');
341
342 if (!current_user_can('manage_options')) {
343 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
344 }
345
346 $name = isset($_POST['name']) ? $_POST['name'] : '';
347 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
348
349 //error_log('[MXCHAT-PROMPTS] Saving setting: ' . $name . ' = ' . $value);
350
351 if (empty($name)) {
352 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
353 }
354
355 // Handle Pinecone settings - BYPASS WORDPRESS SANITIZATION
356 if (strpos($name, 'mxchat_pinecone_addon_options') !== false) {
357 //error_log('[MXCHAT-PROMPTS] Processing Pinecone setting: ' . $name);
358
359 // Extract the field name
360 if (preg_match('/mxchat_pinecone_addon_options\[([^\]]+)\]/', $name, $matches)) {
361 $field_name = $matches[1];
362 //error_log('[MXCHAT-PROMPTS] Extracted field name: ' . $field_name);
363
364 // Get current options directly from database - NO WordPress filters
365 global $wpdb;
366 $current_options_raw = $wpdb->get_var(
367 $wpdb->prepare(
368 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
369 'mxchat_pinecone_addon_options'
370 )
371 );
372
373 // FIX: Handle the case where the option doesn't exist yet
374 if ($current_options_raw === null) {
375 // Option doesn't exist, create it with default values
376 $current_options = array(
377 'mxchat_use_pinecone' => '0',
378 'mxchat_pinecone_api_key' => '',
379 'mxchat_pinecone_host' => '',
380 'mxchat_pinecone_index' => '',
381 'mxchat_pinecone_environment' => ''
382 );
383 //error_log('[MXCHAT-PROMPTS] Option does not exist, creating with defaults');
384 } else {
385 // Unserialize the raw data
386 $current_options = maybe_unserialize($current_options_raw);
387 if (!is_array($current_options)) {
388 // Fallback to defaults if unserialization fails
389 $current_options = array(
390 'mxchat_use_pinecone' => '0',
391 'mxchat_pinecone_api_key' => '',
392 'mxchat_pinecone_host' => '',
393 'mxchat_pinecone_index' => '',
394 'mxchat_pinecone_environment' => ''
395 );
396 //error_log('[MXCHAT-PROMPTS] Failed to unserialize, using defaults');
397 }
398 }
399
400 //error_log('[MXCHAT-PROMPTS] Current options from DB: ' . print_r($current_options, true));
401
402 // Update the specific field with proper sanitization
403 switch ($field_name) {
404 case 'mxchat_use_pinecone':
405 $new_value = ($value === '1') ? '1' : '0';
406 break;
407 case 'mxchat_pinecone_api_key':
408 case 'mxchat_pinecone_host':
409 case 'mxchat_pinecone_index':
410 case 'mxchat_pinecone_environment':
411 $new_value = sanitize_text_field($value);
412 if ($field_name === 'mxchat_pinecone_host') {
413 $new_value = str_replace(['https://', 'http://'], '', $new_value);
414 }
415 break;
416 default:
417 wp_send_json_error(['message' => esc_html__('Unknown Pinecone field', 'mxchat')]);
418 }
419
420 $current_options[$field_name] = $new_value;
421 //error_log('[MXCHAT-PROMPTS] New value for ' . $field_name . ': "' . $new_value . '"');
422 //error_log('[MXCHAT-PROMPTS] Updated options: ' . print_r($current_options, true));
423
424 // Save directly to database to bypass WordPress sanitization
425 $serialized_options = maybe_serialize($current_options);
426
427 // FIX: Use INSERT ... ON DUPLICATE KEY UPDATE or separate INSERT/UPDATE logic
428 $option_exists = $wpdb->get_var(
429 $wpdb->prepare(
430 "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s",
431 'mxchat_pinecone_addon_options'
432 )
433 );
434
435 if ($option_exists > 0) {
436 // Update existing option
437 $save_result = $wpdb->update(
438 $wpdb->options,
439 array('option_value' => $serialized_options),
440 array('option_name' => 'mxchat_pinecone_addon_options'),
441 array('%s'),
442 array('%s')
443 );
444 //error_log('[MXCHAT-PROMPTS] Updated existing option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
445 } else {
446 // Insert new option
447 $save_result = $wpdb->insert(
448 $wpdb->options,
449 array(
450 'option_name' => 'mxchat_pinecone_addon_options',
451 'option_value' => $serialized_options,
452 'autoload' => 'yes'
453 ),
454 array('%s', '%s', '%s')
455 );
456 //error_log('[MXCHAT-PROMPTS] Inserted new option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
457 }
458
459 // Clear any WordPress option cache to ensure get_option() returns fresh data
460 wp_cache_delete('mxchat_pinecone_addon_options', 'options');
461
462 // IMPROVED VERIFICATION - Check if the database operation succeeded
463 if ($save_result !== false) {
464 // Double-check by reading fresh from database
465 $verification_raw = $wpdb->get_var(
466 $wpdb->prepare(
467 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
468 'mxchat_pinecone_addon_options'
469 )
470 );
471 $verification_options = maybe_unserialize($verification_raw);
472 $verified_value = isset($verification_options[$field_name]) ? $verification_options[$field_name] : 'NOT_FOUND';
473
474 //error_log('[MXCHAT-PROMPTS] Final verification - Expected: "' . $new_value . '", Got: "' . $verified_value . '"');
475
476 // Use loose comparison (==) instead of strict (===) to avoid type issues
477 if ($verified_value == $new_value || $save_result > 0) {
478 wp_send_json_success(['message' => esc_html__('Pinecone setting saved', 'mxchat')]);
479 } else {
480 // Still return success if the DB operation worked, even if verification is quirky
481 //error_log('[MXCHAT-PROMPTS] Verification mismatch but DB operation succeeded');
482 wp_send_json_success(['message' => esc_html__('Pinecone setting saved (DB success)', 'mxchat')]);
483 }
484 } else {
485 wp_send_json_error(['message' => esc_html__('Database save failed', 'mxchat')]);
486 }
487 } else {
488 wp_send_json_error(['message' => esc_html__('Invalid field name format', 'mxchat')]);
489 }
490
491 return; // Exit here for Pinecone settings
492 }
493 // Handle auto-sync settings (existing functionality)
494 if (strpos($name, 'mxchat_auto_sync_') === 0) {
495 $value = ($value === 'on' || $value === '1') ? '1' : '0';
496 $updated = update_option($name, $value);
497
498 if ($updated || get_option($name) === $value) {
499 wp_send_json_success(['message' => esc_html__('Auto-sync setting saved', 'mxchat')]);
500 } else {
501 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
502 }
503 }
504
505 // Handle other prompts options
506 $options = get_option('mxchat_prompts_options', []);
507 $options[$name] = $value;
508 $updated = update_option('mxchat_prompts_options', $options);
509
510 if ($updated) {
511 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
512 } else {
513 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
514 }
515 }
516
517
518 /**
519 * Handles AJAX request for Pinecone settings migration
520 */
521 public function ajax_migrate_pinecone_settings() {
522 // Verify nonce
523 if (!wp_verify_nonce($_POST['_ajax_nonce'] ?? '', 'mxchat_save_setting_nonce')) {
524 wp_send_json_error('Invalid nonce');
525 }
526
527 // Check permissions
528 if (!current_user_can('manage_options')) {
529 wp_send_json_error('Unauthorized access');
530 }
531
532 // Check if old Pinecone addon options exist
533 $old_options = get_option('mxchat_pinecone_addon_options', array());
534
535 if (empty($old_options)) {
536 wp_send_json_success(array('migrated' => false, 'message' => 'No old settings found'));
537 }
538
539 // Get current core plugin options
540 $current_options = get_option('mxchat_pinecone_addon_options', array());
541
542 // Only migrate if core options are empty or if explicitly requested
543 $should_migrate = empty($current_options) ||
544 (empty($current_options['mxchat_pinecone_api_key']) && !empty($old_options['mxchat_pinecone_api_key']));
545
546 if ($should_migrate) {
547 // Migrate settings with proper sanitization
548 $migrated_options = array(
549 'mxchat_use_pinecone' => $old_options['mxchat_use_pinecone'] ?? '0',
550 'mxchat_pinecone_api_key' => sanitize_text_field($old_options['mxchat_pinecone_api_key'] ?? ''),
551 'mxchat_pinecone_host' => sanitize_text_field($old_options['mxchat_pinecone_host'] ?? ''),
552 'mxchat_pinecone_index' => sanitize_text_field($old_options['mxchat_pinecone_index'] ?? ''),
553 'mxchat_pinecone_environment' => sanitize_text_field($old_options['mxchat_pinecone_environment'] ?? '')
554 );
555
556 update_option('mxchat_pinecone_addon_options', $migrated_options);
557
558 wp_send_json_success(array(
559 'migrated' => true,
560 'message' => 'Settings migrated successfully from Pinecone add-on'
561 ));
562 } else {
563 wp_send_json_success(array(
564 'migrated' => false,
565 'message' => 'Settings already exist in core plugin'
566 ));
567 }
568 }
569
570
571 // ========================================
572 // LICENSE AJAX HANDLERS
573 // ========================================
574
575 /**
576 * Validates and activates chat license via AJAX
577 */
578 public function mxchat_handle_activate_license() {
579 // Check nonce
580 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
581 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
582 return;
583 }
584
585 // Verify user capabilities
586 if (!current_user_can('manage_options')) {
587 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
588 return;
589 }
590
591 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
592 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
593
594 if (empty($license_key) || empty($customer_email)) {
595 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
596 return;
597 }
598
599 $product_id = 'MxChatPRO';
600 $domain = parse_url(home_url(), PHP_URL_HOST); // Get the current domain
601
602 // Call WooCommerce Software API for activation (not just validation)
603 $response = wp_remote_get(
604 add_query_arg(
605 array(
606 'wc-api' => 'software-api',
607 'request' => 'activation',
608 'email' => $customer_email,
609 'license_key' => $license_key,
610 'product_id' => $product_id,
611 'instance' => $domain, // THIS IS KEY - include the domain as instance
612 'platform' => 'wordpress' // Optional but good to include
613 ),
614 'https://mxchat.ai/'
615 ),
616 array(
617 'timeout' => 60,
618 'sslverify' => true
619 )
620 );
621
622 if (is_wp_error($response)) {
623 $error_message = $response->get_error_message();
624 //error_log('MxChat License Activation Error: ' . $error_message);
625 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $error_message);
626 return;
627 }
628
629 $response_code = wp_remote_retrieve_response_code($response);
630 $body = wp_remote_retrieve_body($response);
631
632 // Log response for debugging
633 //error_log('MxChat License Response Code: ' . $response_code);
634 //error_log('MxChat License Response Body: ' . $body);
635
636 if ($response_code !== 200) {
637 wp_send_json_error(esc_html__('Server returned error code: ', 'mxchat') . $response_code);
638 return;
639 }
640
641 $data = json_decode($body);
642
643 if ($data && isset($data->activated) && $data->activated) {
644 // Success - save local options
645 update_option('mxchat_license_status', 'active');
646 update_option('mxchat_pro_email', $customer_email);
647 update_option('mxchat_activation_key', $license_key);
648 delete_option('mxchat_license_error');
649
650 // Also track on your website (this is your existing domain tracking)
651 $this->track_domain_on_website($license_key, $customer_email, $domain);
652
653 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
654 } else {
655 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
656 update_option('mxchat_license_status', 'inactive');
657 update_option('mxchat_license_error', $error_message);
658
659 //error_log('MxChat Activation failed: ' . $error_message);
660 wp_send_json_error($error_message);
661 }
662 }
663
664 /**
665 * Track domain on your website (separate from WooCommerce activation)
666 */
667 private function track_domain_on_website($license_key, $email, $domain) {
668 // This calls your website's tracking API
669 wp_remote_post('https://mxchat.ai/mxchat-api/activate-license', array(
670 'body' => array(
671 'mxchat_pro_email' => $email,
672 'mxchat_activation_key' => $license_key,
673 'domain' => $domain
674 ),
675 'timeout' => 10,
676 'sslverify' => true
677 ));
678 }
679
680 /**
681 * Validates license via AJAX with email and key
682 */
683 public function mxchat_check_license_status() {
684 // Verify nonce
685 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
686 wp_send_json_error('Security check failed');
687 return;
688 }
689
690 // Add isset checks for safety
691 $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
692 $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : '';
693
694 // Check if this license is actually active in your system
695 $is_active = (get_option('mxchat_license_status') === 'active' &&
696 get_option('mxchat_pro_email') === $email &&
697 get_option('mxchat_activation_key') === $key);
698
699 wp_send_json(array(
700 'is_active' => $is_active
701 ));
702 }
703
704 /**
705 * Handle license deactivation - Complete version for plugin
706 */
707 function mxchat_deactivate_license() {
708 // Add debugging
709 //error_log('MxChat deactivate function called');
710
711 // Check nonce
712 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
713 //error_log('MxChat deactivate: Nonce check failed');
714 wp_send_json_error('Security check failed.');
715 return;
716 }
717
718 //error_log('MxChat deactivate: Nonce check passed');
719
720 $license_key = get_option('mxchat_activation_key');
721 $email = get_option('mxchat_pro_email');
722 $domain = parse_url(home_url(), PHP_URL_HOST);
723
724 //error_log('MxChat deactivate: License: ' . $license_key . ', Email: ' . $email . ', Domain: ' . $domain);
725
726 if (empty($license_key) || empty($email)) {
727 //error_log('MxChat deactivate: No active license found');
728 wp_send_json_error('No active license found.');
729 return;
730 }
731
732 // Clear local license data first
733 delete_option('mxchat_license_status');
734 delete_option('mxchat_pro_email');
735 delete_option('mxchat_activation_key');
736 delete_option('mxchat_license_error');
737
738 //error_log('MxChat deactivate: Local data cleared');
739
740 // Notify your website's API to properly deactivate
741 $response = wp_remote_post('https://mxchat.ai/mxchat-api/deactivate-license', array(
742 'body' => array(
743 'license_key' => $license_key,
744 'email' => $email,
745 'domain' => $domain
746 ),
747 'timeout' => 15,
748 'sslverify' => true
749 ));
750
751 if (is_wp_error($response)) {
752 //error_log('MxChat deactivate: Server error - ' . $response->get_error_message());
753 wp_send_json_success(array(
754 'message' => 'License deactivated locally. Server could not be contacted to free activation slot.',
755 'server_notified' => false
756 ));
757 return;
758 }
759
760 $response_body = wp_remote_retrieve_body($response);
761 $response_data = json_decode($response_body, true);
762
763 //error_log('MxChat deactivate: Server response - ' . $response_body);
764
765 if (isset($response_data['success']) && $response_data['success']) {
766 //error_log('MxChat deactivate: Success with server notification');
767 wp_send_json_success(array(
768 'message' => 'License deactivated successfully. Activation slot has been freed up.',
769 'server_notified' => true
770 ));
771 } else {
772 //error_log('MxChat deactivate: Server responded but deactivation may have failed');
773 wp_send_json_success(array(
774 'message' => 'License deactivated locally. Please check your account dashboard to verify the activation was freed.',
775 'server_notified' => false
776 ));
777 }
778 }
779
780
781 // ========================================
782 // ACTIONS & INTENTS AJAX HANDLERS
783 // ========================================
784
785 /**
786 * Validates nonce and returns JSON error on failure
787 */
788 public function mxchat_toggle_action() {
789 // Check nonce
790 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
791 wp_send_json_error(array('message' => 'Security check failed'));
792 return;
793 }
794
795 // Check permissions
796 if (!current_user_can('manage_options')) {
797 wp_send_json_error(array('message' => 'Permission denied'));
798 return;
799 }
800
801 // Validate params
802 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
803 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
804
805 if (!$intent_id) {
806 wp_send_json_error(array('message' => 'Invalid action ID'));
807 return;
808 }
809
810 // Update the intent/action status in the database
811 global $wpdb;
812 $table_name = $wpdb->prefix . 'mxchat_intents';
813
814 // Using the 'enabled' field - add this field if it doesn't exist
815 $result = $wpdb->update(
816 $table_name,
817 array('enabled' => $enabled ? 1 : 0),
818 array('id' => $intent_id),
819 array('%d'),
820 array('%d')
821 );
822
823 if ($result === false) {
824 wp_send_json_error(array('message' => 'Database error'));
825 return;
826 }
827
828 wp_send_json_success();
829 }
830
831
832 /**
833 * Validates permissions for AJAX request handling
834 */
835 public function mxchat_update_intent_threshold() {
836 // Check permissions
837 if (!current_user_can('manage_options')) {
838 if (wp_doing_ajax()) {
839 wp_send_json_error(array('message' => 'Unauthorized user'));
840 return;
841 }
842 wp_die(esc_html__('Unauthorized user', 'mxchat'));
843 }
844
845 // Verify nonce
846 check_admin_referer('mxchat_update_intent_threshold_nonce');
847
848 // Process the update if we have valid data
849 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
850 global $wpdb;
851 $table_name = $wpdb->prefix . 'mxchat_intents';
852 $intent_id = intval($_POST['intent_id']);
853 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
854 $similarity_threshold = $threshold_percentage / 100;
855
856 $result = $wpdb->update(
857 $table_name,
858 ['similarity_threshold' => $similarity_threshold],
859 ['id' => $intent_id],
860 ['%f'],
861 ['%d']
862 );
863
864 // Handle AJAX requests
865 if (wp_doing_ajax()) {
866 if ($result === false) {
867 wp_send_json_error(array('message' => 'Failed to update threshold'));
868 } else {
869 wp_send_json_success(array('threshold' => $threshold_percentage));
870 }
871 return;
872 }
873 }
874
875 // Redirect for regular form submissions
876 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
877 exit;
878 }
879
880 // ========================================
881 // HELPER METHODS
882 // ========================================
883
884 /**
885 * Returns a specific nonce action string
886 */
887 private function mxchat_get_nonce_action() {
888 return 'mxchat_license_nonce';
889 }
890
891 /**
892 * Check API key status for all providers
893 */
894 public function mxchat_check_api_keys() {
895 // Check nonce
896 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) {
897 wp_send_json_error('Invalid nonce');
898 }
899
900 // Check permissions
901 if (!current_user_can('manage_options')) {
902 wp_send_json_error('Unauthorized');
903 }
904
905 // Get current options
906 $options = get_option('mxchat_options', array());
907
908 // Check which API keys are present
909 $api_key_status = array(
910 'openai' => !empty($options['api_key']),
911 'claude' => !empty($options['claude_api_key']),
912 'xai' => !empty($options['xai_api_key']),
913 'deepseek' => !empty($options['deepseek_api_key']),
914 'gemini' => !empty($options['gemini_api_key']),
915 'openrouter' => !empty($options['openrouter_api_key']),
916 'voyage' => !empty($options['voyage_api_key'])
917 );
918
919 wp_send_json_success($api_key_status);
920 }
921
922 }
923
924 // Initialize the AJAX handler
925 new MxChat_Ajax_Handler();
926