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

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