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

1,279 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 ])) {
376 //error_log('MXChat Save: Processing toggle: ' . $field_name);
377 $options[$field_name] = ($value === 'on') ? 'on' : 'off';
378 } else {
379 //error_log('MXChat Save: Processing standard field: ' . $field_name);
380 // Store all other values directly using the extracted field name
381 $options[$field_name] = $value;
382 }
383 break;
384 }
385
386 // Save all updates to the options array
387 $updated = update_option('mxchat_options', $options);
388 //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
389 //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
390
391 // Log the save action if debug mode is enabled
392 if ( class_exists( 'MxChat_Admin' ) ) {
393 MxChat_Admin::mxchat_log_debug(
394 'settings_save',
395 sprintf( 'Field saved: %s', $field_name ),
396 array(
397 'field' => $field_name,
398 'updated' => $updated,
399 )
400 );
401 }
402
403 // Always return success even if WordPress says nothing changed
404 // (which happens when the value is the same as before)
405 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
406 }
407
408 /**
409 * Save the selected bot for knowledge base operations
410 */
411 public function mxchat_save_selected_bot() {
412 // Check nonce
413 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) {
414 wp_send_json_error('Invalid nonce');
415 }
416
417 // Check permissions
418 if (!current_user_can('manage_options')) {
419 wp_send_json_error('Unauthorized');
420 }
421
422 $bot_id = isset($_POST['bot_id']) ? sanitize_key($_POST['bot_id']) : 'default';
423
424 // Save as user meta for the current user
425 $user_id = get_current_user_id();
426 update_user_meta($user_id, 'mxchat_selected_knowledge_bot', $bot_id);
427
428 // Also save as an option for site-wide default
429 update_option('mxchat_current_knowledge_bot', $bot_id);
430
431 // No cache clearing needed since we removed caching
432
433 wp_send_json_success(array(
434 'message' => 'Bot selection saved',
435 'bot_id' => $bot_id
436 ));
437 }
438
439 /**
440 * Handles AJAX request for saving chat settings
441 */
442 public function mxchat_save_prompts_setting_callback() {
443 check_ajax_referer('mxchat_prompts_setting_nonce');
444
445 if (!current_user_can('manage_options')) {
446 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
447 }
448
449 $name = isset($_POST['name']) ? $_POST['name'] : '';
450 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
451
452 //error_log('[MXCHAT-PROMPTS] Saving setting: ' . $name . ' = ' . $value);
453
454 if (empty($name)) {
455 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
456 }
457
458 // Handle Pinecone settings - BYPASS WORDPRESS SANITIZATION
459 if (strpos($name, 'mxchat_pinecone_addon_options') !== false) {
460 //error_log('[MXCHAT-PROMPTS] Processing Pinecone setting: ' . $name);
461
462 // Extract the field name
463 if (preg_match('/mxchat_pinecone_addon_options\[([^\]]+)\]/', $name, $matches)) {
464 $field_name = $matches[1];
465 //error_log('[MXCHAT-PROMPTS] Extracted field name: ' . $field_name);
466
467 // Get current options directly from database - NO WordPress filters
468 global $wpdb;
469 $current_options_raw = $wpdb->get_var(
470 $wpdb->prepare(
471 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
472 'mxchat_pinecone_addon_options'
473 )
474 );
475
476 // FIX: Handle the case where the option doesn't exist yet
477 if ($current_options_raw === null) {
478 // Option doesn't exist, create it with default values
479 $current_options = array(
480 'mxchat_use_pinecone' => '0',
481 'mxchat_pinecone_api_key' => '',
482 'mxchat_pinecone_host' => '',
483 'mxchat_pinecone_index' => '',
484 'mxchat_pinecone_environment' => ''
485 );
486 //error_log('[MXCHAT-PROMPTS] Option does not exist, creating with defaults');
487 } else {
488 // Unserialize the raw data
489 $current_options = maybe_unserialize($current_options_raw);
490 if (!is_array($current_options)) {
491 // Fallback to defaults if unserialization fails
492 $current_options = array(
493 'mxchat_use_pinecone' => '0',
494 'mxchat_pinecone_api_key' => '',
495 'mxchat_pinecone_host' => '',
496 'mxchat_pinecone_index' => '',
497 'mxchat_pinecone_environment' => ''
498 );
499 //error_log('[MXCHAT-PROMPTS] Failed to unserialize, using defaults');
500 }
501 }
502
503 //error_log('[MXCHAT-PROMPTS] Current options from DB: ' . print_r($current_options, true));
504
505 // Update the specific field with proper sanitization
506 switch ($field_name) {
507 case 'mxchat_use_pinecone':
508 $new_value = ($value === '1') ? '1' : '0';
509 break;
510 case 'mxchat_pinecone_api_key':
511 case 'mxchat_pinecone_host':
512 case 'mxchat_pinecone_index':
513 case 'mxchat_pinecone_environment':
514 $new_value = sanitize_text_field($value);
515 if ($field_name === 'mxchat_pinecone_host') {
516 $new_value = str_replace(['https://', 'http://'], '', $new_value);
517 }
518 break;
519 default:
520 wp_send_json_error(['message' => esc_html__('Unknown Pinecone field', 'mxchat')]);
521 }
522
523 $current_options[$field_name] = $new_value;
524 //error_log('[MXCHAT-PROMPTS] New value for ' . $field_name . ': "' . $new_value . '"');
525 //error_log('[MXCHAT-PROMPTS] Updated options: ' . print_r($current_options, true));
526
527 // Save directly to database to bypass WordPress sanitization
528 $serialized_options = maybe_serialize($current_options);
529
530 // FIX: Use INSERT ... ON DUPLICATE KEY UPDATE or separate INSERT/UPDATE logic
531 $option_exists = $wpdb->get_var(
532 $wpdb->prepare(
533 "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s",
534 'mxchat_pinecone_addon_options'
535 )
536 );
537
538 if ($option_exists > 0) {
539 // Update existing option
540 $save_result = $wpdb->update(
541 $wpdb->options,
542 array('option_value' => $serialized_options),
543 array('option_name' => 'mxchat_pinecone_addon_options'),
544 array('%s'),
545 array('%s')
546 );
547 //error_log('[MXCHAT-PROMPTS] Updated existing option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
548 } else {
549 // Insert new option
550 $save_result = $wpdb->insert(
551 $wpdb->options,
552 array(
553 'option_name' => 'mxchat_pinecone_addon_options',
554 'option_value' => $serialized_options,
555 'autoload' => 'yes'
556 ),
557 array('%s', '%s', '%s')
558 );
559 //error_log('[MXCHAT-PROMPTS] Inserted new option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
560 }
561
562 // Clear any WordPress option cache to ensure get_option() returns fresh data
563 wp_cache_delete('mxchat_pinecone_addon_options', 'options');
564
565 // IMPROVED VERIFICATION - Check if the database operation succeeded
566 if ($save_result !== false) {
567 // Double-check by reading fresh from database
568 $verification_raw = $wpdb->get_var(
569 $wpdb->prepare(
570 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
571 'mxchat_pinecone_addon_options'
572 )
573 );
574 $verification_options = maybe_unserialize($verification_raw);
575 $verified_value = isset($verification_options[$field_name]) ? $verification_options[$field_name] : 'NOT_FOUND';
576
577 //error_log('[MXCHAT-PROMPTS] Final verification - Expected: "' . $new_value . '", Got: "' . $verified_value . '"');
578
579 // Use loose comparison (==) instead of strict (===) to avoid type issues
580 if ($verified_value == $new_value || $save_result > 0) {
581 wp_send_json_success(['message' => esc_html__('Pinecone setting saved', 'mxchat')]);
582 } else {
583 // Still return success if the DB operation worked, even if verification is quirky
584 //error_log('[MXCHAT-PROMPTS] Verification mismatch but DB operation succeeded');
585 wp_send_json_success(['message' => esc_html__('Pinecone setting saved (DB success)', 'mxchat')]);
586 }
587 } else {
588 wp_send_json_error(['message' => esc_html__('Database save failed', 'mxchat')]);
589 }
590 } else {
591 wp_send_json_error(['message' => esc_html__('Invalid field name format', 'mxchat')]);
592 }
593
594 return; // Exit here for Pinecone settings
595 }
596 // Handle auto-sync settings (existing functionality)
597 if (strpos($name, 'mxchat_auto_sync_') === 0) {
598 $value = ($value === 'on' || $value === '1') ? '1' : '0';
599 $updated = update_option($name, $value);
600
601 if ($updated || get_option($name) === $value) {
602 wp_send_json_success(['message' => esc_html__('Auto-sync setting saved', 'mxchat')]);
603 } else {
604 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
605 }
606 }
607
608 // Handle chunking settings - use direct DB access to bypass WordPress filters
609 if (strpos($name, 'mxchat_chunk') === 0 || $name === 'mxchat_chunking_enabled') {
610 global $wpdb;
611
612 // Get current options directly from database
613 $current_options_raw = $wpdb->get_var(
614 $wpdb->prepare(
615 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
616 'mxchat_options'
617 )
618 );
619
620 $options = $current_options_raw !== null ? maybe_unserialize($current_options_raw) : array();
621 if (!is_array($options)) {
622 $options = array();
623 }
624
625 // Update the specific chunking field
626 if ($name === 'mxchat_chunking_enabled') {
627 $options['chunking_enabled'] = in_array($value, array('on', '1', 'true', true), true);
628 } elseif ($name === 'mxchat_chunk_size') {
629 $options['chunk_size'] = max(1000, min(10000, intval($value)));
630 }
631
632 // Save directly to database
633 $serialized_options = maybe_serialize($options);
634
635 $option_exists = $wpdb->get_var(
636 $wpdb->prepare(
637 "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s",
638 'mxchat_options'
639 )
640 );
641
642 if ($option_exists > 0) {
643 $save_result = $wpdb->update(
644 $wpdb->options,
645 array('option_value' => $serialized_options),
646 array('option_name' => 'mxchat_options'),
647 array('%s'),
648 array('%s')
649 );
650 } else {
651 $save_result = $wpdb->insert(
652 $wpdb->options,
653 array(
654 'option_name' => 'mxchat_options',
655 'option_value' => $serialized_options,
656 'autoload' => 'yes'
657 ),
658 array('%s', '%s', '%s')
659 );
660 }
661
662 // Clear object cache for this option
663 wp_cache_delete('mxchat_options', 'options');
664
665 if ($save_result !== false) {
666 wp_send_json_success(['message' => esc_html__('Chunking setting saved', 'mxchat')]);
667 } else {
668 wp_send_json_error(['message' => esc_html__('Failed to save chunking setting', 'mxchat')]);
669 }
670 return;
671 }
672
673 // Handle ACF field exclusion toggles
674 if (strpos($name, 'mxchat_acf_field_') === 0) {
675 // Extract field name from the input name (e.g., mxchat_acf_field_private_notes -> private_notes)
676 $field_name = str_replace('mxchat_acf_field_', '', $name);
677 $is_enabled = ($value === 'on' || $value === '1');
678
679 // Get current excluded fields
680 $excluded_fields = get_option('mxchat_acf_excluded_fields', array());
681 if (!is_array($excluded_fields)) {
682 $excluded_fields = array();
683 }
684
685 if ($is_enabled) {
686 // Remove from exclusion list (field should be included)
687 $excluded_fields = array_values(array_diff($excluded_fields, array($field_name)));
688 } else {
689 // Add to exclusion list (field should be excluded)
690 if (!in_array($field_name, $excluded_fields)) {
691 $excluded_fields[] = $field_name;
692 }
693 }
694
695 $updated = update_option('mxchat_acf_excluded_fields', $excluded_fields);
696
697 if ($updated || true) { // Always report success since the state may already be correct
698 wp_send_json_success([
699 'message' => $is_enabled
700 ? sprintf(esc_html__('Field "%s" will be included in imports', 'mxchat'), $field_name)
701 : sprintf(esc_html__('Field "%s" will be excluded from imports', 'mxchat'), $field_name)
702 ]);
703 } else {
704 wp_send_json_error(['message' => esc_html__('Failed to save ACF field setting', 'mxchat')]);
705 }
706 return;
707 }
708
709 // Handle custom post meta whitelist
710 if ($name === 'mxchat_custom_meta_whitelist') {
711 $updated = update_option('mxchat_custom_meta_whitelist', sanitize_textarea_field($value));
712
713 if ($updated || true) { // Always report success since the state may already be correct
714 wp_send_json_success([
715 'message' => esc_html__('Custom meta whitelist saved', 'mxchat')
716 ]);
717 } else {
718 wp_send_json_error(['message' => esc_html__('Failed to save custom meta whitelist', 'mxchat')]);
719 }
720 return;
721 }
722
723 // Handle other prompts options
724 $options = get_option('mxchat_prompts_options', []);
725 $options[$name] = $value;
726 $updated = update_option('mxchat_prompts_options', $options);
727
728 if ($updated) {
729 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
730 } else {
731 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
732 }
733 }
734
735
736 /**
737 * Handles AJAX request for Pinecone settings migration
738 */
739 public function ajax_migrate_pinecone_settings() {
740 // Verify nonce
741 if (!wp_verify_nonce($_POST['_ajax_nonce'] ?? '', 'mxchat_save_setting_nonce')) {
742 wp_send_json_error('Invalid nonce');
743 }
744
745 // Check permissions
746 if (!current_user_can('manage_options')) {
747 wp_send_json_error('Unauthorized access');
748 }
749
750 // Check if old Pinecone addon options exist
751 $old_options = get_option('mxchat_pinecone_addon_options', array());
752
753 if (empty($old_options)) {
754 wp_send_json_success(array('migrated' => false, 'message' => 'No old settings found'));
755 }
756
757 // Get current core plugin options
758 $current_options = get_option('mxchat_pinecone_addon_options', array());
759
760 // Only migrate if core options are empty or if explicitly requested
761 $should_migrate = empty($current_options) ||
762 (empty($current_options['mxchat_pinecone_api_key']) && !empty($old_options['mxchat_pinecone_api_key']));
763
764 if ($should_migrate) {
765 // Migrate settings with proper sanitization
766 $migrated_options = array(
767 'mxchat_use_pinecone' => $old_options['mxchat_use_pinecone'] ?? '0',
768 'mxchat_pinecone_api_key' => sanitize_text_field($old_options['mxchat_pinecone_api_key'] ?? ''),
769 'mxchat_pinecone_host' => sanitize_text_field($old_options['mxchat_pinecone_host'] ?? ''),
770 'mxchat_pinecone_index' => sanitize_text_field($old_options['mxchat_pinecone_index'] ?? ''),
771 'mxchat_pinecone_environment' => sanitize_text_field($old_options['mxchat_pinecone_environment'] ?? '')
772 );
773
774 update_option('mxchat_pinecone_addon_options', $migrated_options);
775
776 wp_send_json_success(array(
777 'migrated' => true,
778 'message' => 'Settings migrated successfully from Pinecone add-on'
779 ));
780 } else {
781 wp_send_json_success(array(
782 'migrated' => false,
783 'message' => 'Settings already exist in core plugin'
784 ));
785 }
786 }
787
788
789 // ========================================
790 // LICENSE AJAX HANDLERS
791 // ========================================
792
793 /**
794 * Validates and activates chat license via AJAX
795 */
796 public function mxchat_handle_activate_license() {
797 // Check nonce
798 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
799 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
800 return;
801 }
802
803 // Verify user capabilities
804 if (!current_user_can('manage_options')) {
805 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
806 return;
807 }
808
809 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
810 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
811
812 if (empty($license_key) || empty($customer_email)) {
813 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
814 return;
815 }
816
817 $product_id = 'MxChatPRO';
818 $domain = parse_url(home_url(), PHP_URL_HOST); // Get the current domain
819
820 // Call WooCommerce Software API for activation (not just validation)
821 $response = wp_remote_get(
822 add_query_arg(
823 array(
824 'wc-api' => 'software-api',
825 'request' => 'activation',
826 'email' => $customer_email,
827 'license_key' => $license_key,
828 'product_id' => $product_id,
829 'instance' => $domain, // THIS IS KEY - include the domain as instance
830 'platform' => 'wordpress' // Optional but good to include
831 ),
832 'https://mxchat.ai/'
833 ),
834 array(
835 'timeout' => 60,
836 'sslverify' => true
837 )
838 );
839
840 if (is_wp_error($response)) {
841 $error_message = $response->get_error_message();
842 //error_log('MxChat License Activation Error: ' . $error_message);
843 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $error_message);
844 return;
845 }
846
847 $response_code = wp_remote_retrieve_response_code($response);
848 $body = wp_remote_retrieve_body($response);
849
850 // Log response for debugging
851 //error_log('MxChat License Response Code: ' . $response_code);
852 //error_log('MxChat License Response Body: ' . $body);
853
854 if ($response_code !== 200) {
855 wp_send_json_error(esc_html__('Server returned error code: ', 'mxchat') . $response_code);
856 return;
857 }
858
859 $data = json_decode($body);
860
861 if ($data && isset($data->activated) && $data->activated) {
862 // Success - save local options
863 update_option('mxchat_license_status', 'active');
864 update_option('mxchat_pro_email', $customer_email);
865 update_option('mxchat_activation_key', $license_key);
866 delete_option('mxchat_license_error');
867
868 // Also track on your website (this is your existing domain tracking)
869 $this->track_domain_on_website($license_key, $customer_email, $domain);
870
871 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
872 } else {
873 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
874 update_option('mxchat_license_status', 'inactive');
875 update_option('mxchat_license_error', $error_message);
876
877 //error_log('MxChat Activation failed: ' . $error_message);
878 wp_send_json_error($error_message);
879 }
880 }
881
882 /**
883 * Track domain on your website (separate from WooCommerce activation)
884 */
885 private function track_domain_on_website($license_key, $email, $domain) {
886 // This calls your website's tracking API
887 wp_remote_post('https://mxchat.ai/mxchat-api/activate-license', array(
888 'body' => array(
889 'mxchat_pro_email' => $email,
890 'mxchat_activation_key' => $license_key,
891 'domain' => $domain
892 ),
893 'timeout' => 10,
894 'sslverify' => true
895 ));
896 }
897
898 /**
899 * Validates license via AJAX with email and key
900 */
901 public function mxchat_check_license_status() {
902 // Verify nonce
903 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
904 wp_send_json_error('Security check failed');
905 return;
906 }
907
908 // Add isset checks for safety
909 $email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
910 $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : '';
911
912 // Check if this license is actually active in your system
913 $is_active = (get_option('mxchat_license_status') === 'active' &&
914 get_option('mxchat_pro_email') === $email &&
915 get_option('mxchat_activation_key') === $key);
916
917 wp_send_json(array(
918 'is_active' => $is_active
919 ));
920 }
921
922 /**
923 * Handle license deactivation - Complete version for plugin
924 */
925 function mxchat_deactivate_license() {
926 // Add debugging
927 //error_log('MxChat deactivate function called');
928
929 // Check nonce
930 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
931 //error_log('MxChat deactivate: Nonce check failed');
932 wp_send_json_error('Security check failed.');
933 return;
934 }
935
936 //error_log('MxChat deactivate: Nonce check passed');
937
938 $license_key = get_option('mxchat_activation_key');
939 $email = get_option('mxchat_pro_email');
940 $domain = parse_url(home_url(), PHP_URL_HOST);
941
942 //error_log('MxChat deactivate: License: ' . $license_key . ', Email: ' . $email . ', Domain: ' . $domain);
943
944 if (empty($license_key) || empty($email)) {
945 //error_log('MxChat deactivate: No active license found');
946 wp_send_json_error('No active license found.');
947 return;
948 }
949
950 // Clear local license data first
951 delete_option('mxchat_license_status');
952 delete_option('mxchat_pro_email');
953 delete_option('mxchat_activation_key');
954 delete_option('mxchat_license_error');
955
956 //error_log('MxChat deactivate: Local data cleared');
957
958 // Notify your website's API to properly deactivate
959 $response = wp_remote_post('https://mxchat.ai/mxchat-api/deactivate-license', array(
960 'body' => array(
961 'license_key' => $license_key,
962 'email' => $email,
963 'domain' => $domain
964 ),
965 'timeout' => 15,
966 'sslverify' => true
967 ));
968
969 if (is_wp_error($response)) {
970 //error_log('MxChat deactivate: Server error - ' . $response->get_error_message());
971 wp_send_json_success(array(
972 'message' => 'License deactivated locally. Server could not be contacted to free activation slot.',
973 'server_notified' => false
974 ));
975 return;
976 }
977
978 $response_body = wp_remote_retrieve_body($response);
979 $response_data = json_decode($response_body, true);
980
981 //error_log('MxChat deactivate: Server response - ' . $response_body);
982
983 if (isset($response_data['success']) && $response_data['success']) {
984 //error_log('MxChat deactivate: Success with server notification');
985 wp_send_json_success(array(
986 'message' => 'License deactivated successfully. Activation slot has been freed up.',
987 'server_notified' => true
988 ));
989 } else {
990 //error_log('MxChat deactivate: Server responded but deactivation may have failed');
991 wp_send_json_success(array(
992 'message' => 'License deactivated locally. Please check your account dashboard to verify the activation was freed.',
993 'server_notified' => false
994 ));
995 }
996 }
997
998
999 // ========================================
1000 // ACTIONS & INTENTS AJAX HANDLERS
1001 // ========================================
1002
1003 /**
1004 * Validates nonce and returns JSON error on failure
1005 */
1006 public function mxchat_toggle_action() {
1007 // Check nonce
1008 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
1009 wp_send_json_error(array('message' => 'Security check failed'));
1010 return;
1011 }
1012
1013 // Check permissions
1014 if (!current_user_can('manage_options')) {
1015 wp_send_json_error(array('message' => 'Permission denied'));
1016 return;
1017 }
1018
1019 // Validate params
1020 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
1021 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
1022
1023 if (!$intent_id) {
1024 wp_send_json_error(array('message' => 'Invalid action ID'));
1025 return;
1026 }
1027
1028 // Update the intent/action status in the database
1029 global $wpdb;
1030 $table_name = $wpdb->prefix . 'mxchat_intents';
1031
1032 // Using the 'enabled' field - add this field if it doesn't exist
1033 $result = $wpdb->update(
1034 $table_name,
1035 array('enabled' => $enabled ? 1 : 0),
1036 array('id' => $intent_id),
1037 array('%d'),
1038 array('%d')
1039 );
1040
1041 if ($result === false) {
1042 wp_send_json_error(array('message' => 'Database error'));
1043 return;
1044 }
1045
1046 wp_send_json_success();
1047 }
1048
1049
1050 /**
1051 * Validates permissions for AJAX request handling
1052 */
1053 public function mxchat_update_intent_threshold() {
1054 // Check permissions
1055 if (!current_user_can('manage_options')) {
1056 if (wp_doing_ajax()) {
1057 wp_send_json_error(array('message' => 'Unauthorized user'));
1058 return;
1059 }
1060 wp_die(esc_html__('Unauthorized user', 'mxchat'));
1061 }
1062
1063 // Verify nonce
1064 check_admin_referer('mxchat_update_intent_threshold_nonce');
1065
1066 // Process the update if we have valid data
1067 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
1068 global $wpdb;
1069 $table_name = $wpdb->prefix . 'mxchat_intents';
1070 $intent_id = intval($_POST['intent_id']);
1071 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
1072 $similarity_threshold = $threshold_percentage / 100;
1073
1074 $result = $wpdb->update(
1075 $table_name,
1076 ['similarity_threshold' => $similarity_threshold],
1077 ['id' => $intent_id],
1078 ['%f'],
1079 ['%d']
1080 );
1081
1082 // Handle AJAX requests
1083 if (wp_doing_ajax()) {
1084 if ($result === false) {
1085 wp_send_json_error(array('message' => 'Failed to update threshold'));
1086 } else {
1087 wp_send_json_success(array('threshold' => $threshold_percentage));
1088 }
1089 return;
1090 }
1091 }
1092
1093 // Redirect for regular form submissions
1094 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
1095 exit;
1096 }
1097
1098 // ========================================
1099 // HELPER METHODS
1100 // ========================================
1101
1102 /**
1103 * Returns a specific nonce action string
1104 */
1105 private function mxchat_get_nonce_action() {
1106 return 'mxchat_license_nonce';
1107 }
1108
1109 /**
1110 * Check API key status for all providers
1111 */
1112 public function mxchat_check_api_keys() {
1113 // Check nonce
1114 if (!wp_verify_nonce($_POST['nonce'] ?? '', 'mxchat_save_setting_nonce')) {
1115 wp_send_json_error('Invalid nonce');
1116 }
1117
1118 // Check permissions
1119 if (!current_user_can('manage_options')) {
1120 wp_send_json_error('Unauthorized');
1121 }
1122
1123 // Get current options
1124 $options = get_option('mxchat_options', array());
1125
1126 // Check which API keys are present
1127 $api_key_status = array(
1128 'openai' => !empty($options['api_key']),
1129 'claude' => !empty($options['claude_api_key']),
1130 'xai' => !empty($options['xai_api_key']),
1131 'deepseek' => !empty($options['deepseek_api_key']),
1132 'gemini' => !empty($options['gemini_api_key']),
1133 'openrouter' => !empty($options['openrouter_api_key']),
1134 'voyage' => !empty($options['voyage_api_key'])
1135 );
1136
1137 wp_send_json_success($api_key_status);
1138 }
1139
1140 // ========================================
1141 // DEBUG & OPTIMIZATION AJAX HANDLERS
1142 // ========================================
1143
1144 /**
1145 * Toggle debug mode on/off
1146 */
1147 public function mxchat_toggle_debug_mode_callback() {
1148 // Verify nonce
1149 if ( ! check_ajax_referer( 'mxchat_save_setting_nonce', '_ajax_nonce', false ) ) {
1150 wp_send_json_error( array( 'message' => esc_html__( 'Security check failed', 'mxchat' ) ) );
1151 }
1152
1153 // Check permissions
1154 if ( ! current_user_can( 'manage_options' ) ) {
1155 wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ) );
1156 }
1157
1158 $enabled = isset( $_POST['enabled'] ) && $_POST['enabled'] === 'on';
1159
1160 $options = get_option( 'mxchat_options', array() );
1161
1162 if ( $enabled ) {
1163 $options['debug_mode'] = 'on';
1164 update_option( 'mxchat_options', $options );
1165 MxChat_Admin::mxchat_log_debug( 'debug_mode', 'Debug mode enabled' );
1166 } else {
1167 // Log before disabling
1168 MxChat_Admin::mxchat_log_debug( 'debug_mode', 'Debug mode disabled' );
1169 $options['debug_mode'] = 'off';
1170 update_option( 'mxchat_options', $options );
1171 }
1172
1173 wp_send_json_success( array(
1174 'message' => $enabled ? esc_html__( 'Debug mode enabled', 'mxchat' ) : esc_html__( 'Debug mode disabled', 'mxchat' ),
1175 'enabled' => $enabled,
1176 ) );
1177 }
1178
1179 /**
1180 * Get the debug log entries
1181 */
1182 public function mxchat_get_debug_log_callback() {
1183 // Verify nonce
1184 if ( ! check_ajax_referer( 'mxchat_save_setting_nonce', '_ajax_nonce', false ) ) {
1185 wp_send_json_error( array( 'message' => esc_html__( 'Security check failed', 'mxchat' ) ) );
1186 }
1187
1188 // Check permissions
1189 if ( ! current_user_can( 'manage_options' ) ) {
1190 wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ) );
1191 }
1192
1193 $log = MxChat_Admin::mxchat_get_debug_log();
1194
1195 wp_send_json_success( array(
1196 'log' => $log,
1197 'count' => count( $log ),
1198 ) );
1199 }
1200
1201 /**
1202 * Clear the debug log
1203 */
1204 public function mxchat_clear_debug_log_callback() {
1205 // Verify nonce
1206 if ( ! check_ajax_referer( 'mxchat_save_setting_nonce', '_ajax_nonce', false ) ) {
1207 wp_send_json_error( array( 'message' => esc_html__( 'Security check failed', 'mxchat' ) ) );
1208 }
1209
1210 // Check permissions
1211 if ( ! current_user_can( 'manage_options' ) ) {
1212 wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ) );
1213 }
1214
1215 MxChat_Admin::mxchat_clear_debug_log();
1216
1217 // Log that the log was cleared (this will be the first entry in the new log)
1218 MxChat_Admin::mxchat_log_debug( 'debug_log', 'Debug log cleared by user' );
1219
1220 wp_send_json_success( array( 'message' => esc_html__( 'Debug log cleared', 'mxchat' ) ) );
1221 }
1222
1223 /**
1224 * Export settings as JSON
1225 */
1226 public function mxchat_export_settings_callback() {
1227 // Verify nonce
1228 if ( ! check_ajax_referer( 'mxchat_save_setting_nonce', '_ajax_nonce', false ) ) {
1229 wp_send_json_error( array( 'message' => esc_html__( 'Security check failed', 'mxchat' ) ) );
1230 }
1231
1232 // Check permissions
1233 if ( ! current_user_can( 'manage_options' ) ) {
1234 wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ) );
1235 }
1236
1237 $export = MxChat_Admin::mxchat_export_settings();
1238
1239 // Log the export
1240 MxChat_Admin::mxchat_log_debug( 'settings_export', 'Settings exported by user' );
1241
1242 wp_send_json_success( array(
1243 'settings' => $export,
1244 'filename' => 'mxchat-settings-' . gmdate( 'Y-m-d-His' ) . '.json',
1245 ) );
1246 }
1247
1248 /**
1249 * Reset all settings to defaults
1250 */
1251 public function mxchat_reset_all_settings_callback() {
1252 // Verify nonce
1253 if ( ! check_ajax_referer( 'mxchat_save_setting_nonce', '_ajax_nonce', false ) ) {
1254 wp_send_json_error( array( 'message' => esc_html__( 'Security check failed', 'mxchat' ) ) );
1255 }
1256
1257 // Check permissions
1258 if ( ! current_user_can( 'manage_options' ) ) {
1259 wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'mxchat' ) ) );
1260 }
1261
1262 // Require confirmation code
1263 $confirmation = isset( $_POST['confirmation'] ) ? sanitize_text_field( wp_unslash( $_POST['confirmation'] ) ) : '';
1264
1265 if ( strtoupper( $confirmation ) !== 'RESET' ) {
1266 wp_send_json_error( array( 'message' => esc_html__( 'Invalid confirmation code. Please type RESET to confirm.', 'mxchat' ) ) );
1267 }
1268
1269 // Perform the reset
1270 MxChat_Admin::mxchat_reset_all_settings();
1271
1272 wp_send_json_success( array( 'message' => esc_html__( 'All settings have been reset to defaults. The page will reload.', 'mxchat' ) ) );
1273 }
1274
1275 }
1276
1277 // Initialize the AJAX handler
1278 new MxChat_Ajax_Handler();
1279