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

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