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

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