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

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