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

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