PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.2
MxChat – AI Chatbot & Content Generation for WordPress v2.2.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / admin / class-ajax-handler.php

class-ajax-handler.php in MxChat – AI Chatbot & Content Generation for WordPress 2.2.2, at admin/class-ajax-handler.php

1,119 lines 46.1 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->init_ajax_hooks();
21 }
22
23 /**
24 * Get Pinecone Manager instance (lazy loading)
25 */
26 private function get_pinecone_manager() {
27 if ($this->pinecone_manager === null) {
28 // Check if the class exists before trying to instantiate
29 if (class_exists('MxChat_Pinecone_Manager')) {
30 $this->pinecone_manager = MxChat_Pinecone_Manager::get_instance();
31 } else {
32 // Fallback: return false if class doesn't exist
33 return false;
34 }
35 }
36 return $this->pinecone_manager;
37 }
38
39 /**
40 * Register all AJAX action hooks
41 */
42 private function init_ajax_hooks() {
43 // Settings AJAX
44 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
45 add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
46 add_action('wp_ajax_migrate_pinecone_settings', array($this, 'ajax_migrate_pinecone_settings'));
47
48 // Content Management AJAX
49 add_action('wp_ajax_mxchat_get_content_list', array($this, 'ajax_mxchat_get_content_list'));
50 add_action('wp_ajax_mxchat_process_selected_content', array($this, 'ajax_mxchat_process_selected_content'));
51 add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt'));
52
53 // License AJAX
54 add_action('wp_ajax_mxchat_activate_license', array($this, 'mxchat_handle_activate_license'));
55 add_action('wp_ajax_mxchat_check_license', array($this, 'mxchat_check_license_status'));
56
57 // Actions & Intents AJAX
58 add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action'));
59 add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold'));
60 }
61
62 // ========================================
63 // SETTINGS AJAX HANDLERS
64 // ========================================
65
66 /**
67 * Validates and saves chat settings via AJAX request
68 */
69 public function mxchat_save_setting_callback() {
70 check_ajax_referer('mxchat_save_setting_nonce');
71 if (!current_user_can('manage_options')) {
72 ('MXChat Save: Unauthorized access attempt');
73 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
74 }
75
76 $name = isset($_POST['name']) ? $_POST['name'] : '';
77 // Strip slashes from the value before saving
78 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
79
80 //error_log('MXChat Save: Processing field name: ' . $name);
81 //error_log('MXChat Save: Field value: ' . $value);
82
83 if (empty($name)) {
84 //error_log('MXChat Save: Empty field name detected');
85 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
86 }
87
88 // Load the full options array
89 $options = get_option('mxchat_options', []);
90 //error_log('MXChat Save: Current options array: ' . print_r($options, true));
91
92 // Handle special cases
93 switch ($name) {
94 case 'additional_popular_questions':
95 //error_log('MXChat Save: Processing additional_popular_questions');
96 $questions = json_decode($value, true); // No need for stripslashes here
97 if (is_array($questions)) {
98 $options[$name] = $questions;
99 // Also update old option for backwards compatibility
100 update_option('additional_popular_questions', $questions);
101 //error_log('MXChat Save: Saved ' . count($questions) . ' additional questions');
102 } else {
103 //error_log('MXChat Save: Failed to decode questions JSON');
104 }
105 break;
106 case 'email_blocker_header_content':
107 //error_log('MXChat Save: Processing email_blocker_header_content');
108 // Allow HTML content but sanitize it safely
109 $options[$name] = wp_kses_post($value);
110 break;
111 case 'similarity_threshold':
112 //error_log('MXChat Save: Processing similarity_threshold');
113 // Save to the options array
114 $options[$name] = $value;
115 break;
116 case 'user_message_bg_color':
117 case 'user_message_font_color':
118 case 'bot_message_bg_color':
119 case 'bot_message_font_color':
120 case 'top_bar_bg_color':
121 case 'send_button_font_color':
122 case 'chatbot_background_color':
123 case 'icon_color':
124 case 'chat_input_font_color':
125 case 'live_agent_message_bg_color':
126 case 'live_agent_message_font_color':
127 case 'mode_indicator_bg_color':
128 case 'mode_indicator_font_color':
129 case 'toolbar_icon_color':
130 //error_log('MXChat Save: Processing color value: ' . $name);
131 // Store color values directly
132 $options[$name] = $value;
133 break;
134 case 'live_agent_status':
135 //error_log('MXChat Save: Processing live_agent_status');
136 // Set the new value
137 $options[$name] = ($value === 'on') ? 'on' : 'off';
138 break;
139 case 'enable_woocommerce_integration':
140 //error_log('MXChat Save: Processing enable_woocommerce_integration');
141 // Handle values that used to be 1/0
142 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
143 break;
144 default:
145 // First check for rate limits settings
146 if (strpos($name, 'mxchat_options[rate_limits]') !== false) {
147 //error_log('MXChat Save: Detected rate_limits field: ' . $name);
148
149 // Extract role ID and setting from the name
150 preg_match('/\[rate_limits\]\[(.*?)\]\[(.*?)\]/', $name, $matches);
151 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
152
153 if (isset($matches[1]) && isset($matches[2])) {
154 $role_id = $matches[1];
155 $setting_key = $matches[2]; // limit, timeframe, or message
156
157 //error_log('MXChat Save: Role ID = ' . $role_id . ', Setting Key = ' . $setting_key);
158
159 // Initialize rate_limits if it doesn't exist
160 if (!isset($options['rate_limits'])) {
161 // //error_log('MXChat Save: Initializing rate_limits array');
162 $options['rate_limits'] = [];
163 }
164
165 // Initialize role settings if it doesn't exist
166 if (!isset($options['rate_limits'][$role_id])) {
167 //error_log('MXChat Save: Initializing rate_limits for role: ' . $role_id);
168 $options['rate_limits'][$role_id] = [
169 'limit' => ($role_id === 'logged_out') ? '10' : '100',
170 'timeframe' => 'daily',
171 'message' => 'Rate limit exceeded. Please try again later.'
172 ];
173 }
174
175 // Update the specific setting
176 $options['rate_limits'][$role_id][$setting_key] = $value;
177 //error_log('MXChat Save: Updated rate_limits[' . $role_id . '][' . $setting_key . '] = ' . $value);
178 } else {
179 //error_log('MXChat Save: Failed to parse rate_limits pattern: ' . $name);
180 }
181 }
182 // Then check for role rate limits (old format)
183 else if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
184 //error_log('MXChat Save: Processing role_rate_limits field: ' . $name);
185 // Extract role ID from the name
186 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
187 //error_log('MXChat Save: Regex matches: ' . print_r($matches, true));
188
189 if (isset($matches[1])) {
190 $role_id = $matches[1];
191 // Initialize role_rate_limits if it doesn't exist
192 if (!isset($options['role_rate_limits'])) {
193 //error_log('MXChat Save: Initializing role_rate_limits array');
194 $options['role_rate_limits'] = [];
195 }
196 // Update the specific role's rate limit
197 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
198 //error_log('MXChat Save: Updated role_rate_limits[' . $role_id . '] = ' . $value);
199 } else {
200 //error_log('MXChat Save: Failed to parse role_rate_limits pattern: ' . $name);
201 }
202 }
203 // Handle toggles
204 else if (strpos($name, 'toggle') !== false || in_array($name, [
205 'chat_persistence_toggle',
206 'privacy_toggle',
207 'complianz_toggle',
208 'chat_toolbar_toggle',
209 'show_pdf_upload_button',
210 'show_word_upload_button'
211 ])) {
212 //error_log('MXChat Save: Processing toggle: ' . $name);
213 $options[$name] = ($value === 'on') ? 'on' : 'off';
214 } else {
215 //error_log('MXChat Save: Processing standard field: ' . $name);
216 // Store all other values directly
217 $options[$name] = $value;
218 }
219 break;
220 }
221
222 // Save all updates to the options array
223 $updated = update_option('mxchat_options', $options);
224 //error_log('MXChat Save: Update result: ' . ($updated ? 'success' : 'unchanged') . ' for field: ' . $name);
225 //error_log('MXChat Save: Updated options array: ' . print_r($options, true));
226
227 // Always return success even if WordPress says nothing changed
228 // (which happens when the value is the same as before)
229 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
230 }
231
232
233 /**
234 * Handles AJAX request for saving chat settings
235 */
236 public function mxchat_save_prompts_setting_callback() {
237 check_ajax_referer('mxchat_prompts_setting_nonce');
238
239 if (!current_user_can('manage_options')) {
240 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
241 }
242
243 $name = isset($_POST['name']) ? $_POST['name'] : '';
244 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
245
246 //error_log('[MXCHAT-PROMPTS] Saving setting: ' . $name . ' = ' . $value);
247
248 if (empty($name)) {
249 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
250 }
251
252 // Handle Pinecone settings - BYPASS WORDPRESS SANITIZATION
253 if (strpos($name, 'mxchat_pinecone_addon_options') !== false) {
254 //error_log('[MXCHAT-PROMPTS] Processing Pinecone setting: ' . $name);
255
256 // Extract the field name
257 if (preg_match('/mxchat_pinecone_addon_options\[([^\]]+)\]/', $name, $matches)) {
258 $field_name = $matches[1];
259 //error_log('[MXCHAT-PROMPTS] Extracted field name: ' . $field_name);
260
261 // Get current options directly from database - NO WordPress filters
262 global $wpdb;
263 $current_options_raw = $wpdb->get_var(
264 $wpdb->prepare(
265 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
266 'mxchat_pinecone_addon_options'
267 )
268 );
269
270 // FIX: Handle the case where the option doesn't exist yet
271 if ($current_options_raw === null) {
272 // Option doesn't exist, create it with default values
273 $current_options = array(
274 'mxchat_use_pinecone' => '0',
275 'mxchat_pinecone_api_key' => '',
276 'mxchat_pinecone_host' => '',
277 'mxchat_pinecone_index' => '',
278 'mxchat_pinecone_environment' => ''
279 );
280 //error_log('[MXCHAT-PROMPTS] Option does not exist, creating with defaults');
281 } else {
282 // Unserialize the raw data
283 $current_options = maybe_unserialize($current_options_raw);
284 if (!is_array($current_options)) {
285 // Fallback to defaults if unserialization fails
286 $current_options = array(
287 'mxchat_use_pinecone' => '0',
288 'mxchat_pinecone_api_key' => '',
289 'mxchat_pinecone_host' => '',
290 'mxchat_pinecone_index' => '',
291 'mxchat_pinecone_environment' => ''
292 );
293 //error_log('[MXCHAT-PROMPTS] Failed to unserialize, using defaults');
294 }
295 }
296
297 //error_log('[MXCHAT-PROMPTS] Current options from DB: ' . print_r($current_options, true));
298
299 // Update the specific field with proper sanitization
300 switch ($field_name) {
301 case 'mxchat_use_pinecone':
302 $new_value = ($value === '1') ? '1' : '0';
303 break;
304 case 'mxchat_pinecone_api_key':
305 case 'mxchat_pinecone_host':
306 case 'mxchat_pinecone_index':
307 case 'mxchat_pinecone_environment':
308 $new_value = sanitize_text_field($value);
309 if ($field_name === 'mxchat_pinecone_host') {
310 $new_value = str_replace(['https://', 'http://'], '', $new_value);
311 }
312 break;
313 default:
314 wp_send_json_error(['message' => esc_html__('Unknown Pinecone field', 'mxchat')]);
315 }
316
317 $current_options[$field_name] = $new_value;
318 //error_log('[MXCHAT-PROMPTS] New value for ' . $field_name . ': "' . $new_value . '"');
319 //error_log('[MXCHAT-PROMPTS] Updated options: ' . print_r($current_options, true));
320
321 // Save directly to database to bypass WordPress sanitization
322 $serialized_options = maybe_serialize($current_options);
323
324 // FIX: Use INSERT ... ON DUPLICATE KEY UPDATE or separate INSERT/UPDATE logic
325 $option_exists = $wpdb->get_var(
326 $wpdb->prepare(
327 "SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name = %s",
328 'mxchat_pinecone_addon_options'
329 )
330 );
331
332 if ($option_exists > 0) {
333 // Update existing option
334 $save_result = $wpdb->update(
335 $wpdb->options,
336 array('option_value' => $serialized_options),
337 array('option_name' => 'mxchat_pinecone_addon_options'),
338 array('%s'),
339 array('%s')
340 );
341 //error_log('[MXCHAT-PROMPTS] Updated existing option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
342 } else {
343 // Insert new option
344 $save_result = $wpdb->insert(
345 $wpdb->options,
346 array(
347 'option_name' => 'mxchat_pinecone_addon_options',
348 'option_value' => $serialized_options,
349 'autoload' => 'yes'
350 ),
351 array('%s', '%s', '%s')
352 );
353 //error_log('[MXCHAT-PROMPTS] Inserted new option, result: ' . ($save_result !== false ? 'SUCCESS' : 'FAILED'));
354 }
355
356 // Clear any WordPress option cache to ensure get_option() returns fresh data
357 wp_cache_delete('mxchat_pinecone_addon_options', 'options');
358
359 // IMPROVED VERIFICATION - Check if the database operation succeeded
360 if ($save_result !== false) {
361 // Double-check by reading fresh from database
362 $verification_raw = $wpdb->get_var(
363 $wpdb->prepare(
364 "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s",
365 'mxchat_pinecone_addon_options'
366 )
367 );
368 $verification_options = maybe_unserialize($verification_raw);
369 $verified_value = isset($verification_options[$field_name]) ? $verification_options[$field_name] : 'NOT_FOUND';
370
371 //error_log('[MXCHAT-PROMPTS] Final verification - Expected: "' . $new_value . '", Got: "' . $verified_value . '"');
372
373 // Use loose comparison (==) instead of strict (===) to avoid type issues
374 if ($verified_value == $new_value || $save_result > 0) {
375 wp_send_json_success(['message' => esc_html__('Pinecone setting saved', 'mxchat')]);
376 } else {
377 // Still return success if the DB operation worked, even if verification is quirky
378 //error_log('[MXCHAT-PROMPTS] Verification mismatch but DB operation succeeded');
379 wp_send_json_success(['message' => esc_html__('Pinecone setting saved (DB success)', 'mxchat')]);
380 }
381 } else {
382 wp_send_json_error(['message' => esc_html__('Database save failed', 'mxchat')]);
383 }
384 } else {
385 wp_send_json_error(['message' => esc_html__('Invalid field name format', 'mxchat')]);
386 }
387
388 return; // Exit here for Pinecone settings
389 }
390 // Handle auto-sync settings (existing functionality)
391 if (strpos($name, 'mxchat_auto_sync_') === 0) {
392 $value = ($value === 'on' || $value === '1') ? '1' : '0';
393 $updated = update_option($name, $value);
394
395 if ($updated || get_option($name) === $value) {
396 wp_send_json_success(['message' => esc_html__('Auto-sync setting saved', 'mxchat')]);
397 } else {
398 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
399 }
400 }
401
402 // Handle other prompts options
403 $options = get_option('mxchat_prompts_options', []);
404 $options[$name] = $value;
405 $updated = update_option('mxchat_prompts_options', $options);
406
407 if ($updated) {
408 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
409 } else {
410 wp_send_json_error(['message' => esc_html__('No changes detected', 'mxchat')]);
411 }
412 }
413
414
415 /**
416 * Handles AJAX request for Pinecone settings migration
417 */
418 public function ajax_migrate_pinecone_settings() {
419 // Verify nonce
420 if (!wp_verify_nonce($_POST['_ajax_nonce'] ?? '', 'mxchat_save_setting_nonce')) {
421 wp_send_json_error('Invalid nonce');
422 }
423
424 // Check permissions
425 if (!current_user_can('manage_options')) {
426 wp_send_json_error('Unauthorized access');
427 }
428
429 // Check if old Pinecone addon options exist
430 $old_options = get_option('mxchat_pinecone_addon_options', array());
431
432 if (empty($old_options)) {
433 wp_send_json_success(array('migrated' => false, 'message' => 'No old settings found'));
434 }
435
436 // Get current core plugin options
437 $current_options = get_option('mxchat_pinecone_addon_options', array());
438
439 // Only migrate if core options are empty or if explicitly requested
440 $should_migrate = empty($current_options) ||
441 (empty($current_options['mxchat_pinecone_api_key']) && !empty($old_options['mxchat_pinecone_api_key']));
442
443 if ($should_migrate) {
444 // Migrate settings with proper sanitization
445 $migrated_options = array(
446 'mxchat_use_pinecone' => $old_options['mxchat_use_pinecone'] ?? '0',
447 'mxchat_pinecone_api_key' => sanitize_text_field($old_options['mxchat_pinecone_api_key'] ?? ''),
448 'mxchat_pinecone_host' => sanitize_text_field($old_options['mxchat_pinecone_host'] ?? ''),
449 'mxchat_pinecone_index' => sanitize_text_field($old_options['mxchat_pinecone_index'] ?? ''),
450 'mxchat_pinecone_environment' => sanitize_text_field($old_options['mxchat_pinecone_environment'] ?? '')
451 );
452
453 update_option('mxchat_pinecone_addon_options', $migrated_options);
454
455 wp_send_json_success(array(
456 'migrated' => true,
457 'message' => 'Settings migrated successfully from Pinecone add-on'
458 ));
459 } else {
460 wp_send_json_success(array(
461 'migrated' => false,
462 'message' => 'Settings already exist in core plugin'
463 ));
464 }
465 }
466
467
468 // ========================================
469 // CONTENT MANAGEMENT AJAX HANDLERS
470 // ========================================
471
472 /**
473 * Handles AJAX request, checks nonce, verifies user permissions
474 */
475 public function ajax_mxchat_get_content_list() {
476 // Verify the nonce
477 check_ajax_referer('mxchat_content_selector_nonce', 'nonce');
478
479 if (!current_user_can('manage_options')) {
480 wp_send_json_error(__('Unauthorized access', 'mxchat'));
481 }
482
483 $page = isset($_GET['page']) ? absint($_GET['page']) : 1;
484 $per_page = isset($_GET['per_page']) ? absint($_GET['per_page']) : 20;
485 $search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
486 $post_type = isset($_GET['post_type']) ? sanitize_text_field($_GET['post_type']) : 'all';
487 $post_status = isset($_GET['post_status']) ? sanitize_text_field($_GET['post_status']) : 'publish';
488 $processed_filter = isset($_GET['processed_filter']) ? sanitize_text_field($_GET['processed_filter']) : 'all';
489
490 // Build query args
491 $args = array(
492 'posts_per_page' => $per_page,
493 'paged' => $page,
494 'post_status' => $post_status !== 'all' ? $post_status : array('publish', 'draft', 'pending'),
495 'orderby' => 'date',
496 'order' => 'DESC',
497 );
498
499 // Handle post types
500 if ($post_type !== 'all') {
501 $args['post_type'] = $post_type;
502 } else {
503 // Default to post and page if we can't get post types
504 $args['post_type'] = array('post', 'page');
505
506 // Try to get public post types
507 $public_types = $this->get_public_post_types();
508 if (is_array($public_types) && !empty($public_types)) {
509 $args['post_type'] = array_keys($public_types);
510 }
511 }
512
513 if (!empty($search)) {
514 $args['s'] = $search;
515 }
516
517 // ================================
518 // UPDATED: Get already vectorized content from BOTH WordPress DB AND Pinecone
519 // ================================
520
521 // FIRST: Get from WordPress DB (your original working code)
522 global $wpdb;
523 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
524 $processed_items = $wpdb->get_results("SELECT id, source_url, timestamp FROM {$table_name}");
525
526 // Create a lookup array of processed posts with timestamps
527 $processed_data = array();
528
529 if (!empty($processed_items)) {
530 foreach ($processed_items as $item) {
531 $post_id = url_to_postid($item->source_url);
532 if ($post_id) {
533 $processed_data[$post_id] = array(
534 'db_id' => $item->id,
535 'timestamp' => $item->timestamp,
536 'url' => $item->source_url,
537 'source' => 'wordpress'
538 );
539 }
540 }
541 }
542
543 // SECOND: ALSO check Pinecone if it's enabled
544 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
545 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
546
547 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
548 // UPDATED: Use lazy-loaded Pinecone manager
549 $pinecone_manager = $this->get_pinecone_manager();
550 if ($pinecone_manager) {
551 $pinecone_data = $pinecone_manager->get_pinecone_processed_content($pinecone_options);
552
553 // Merge Pinecone data with WordPress data
554 foreach ($pinecone_data as $post_id => $pinecone_info) {
555 // If not already found in WordPress DB, add from Pinecone
556 if (!isset($processed_data[$post_id])) {
557 $processed_data[$post_id] = $pinecone_info;
558 }
559 }
560 }
561 }
562
563 // ================================
564
565 // Get processed IDs as a simple array for in_array checks
566 $processed_ids = array_keys($processed_data);
567
568 // Handle processed/unprocessed filter
569 if ($processed_filter === 'processed' && !empty($processed_ids)) {
570 $args['post__in'] = $processed_ids;
571 } elseif ($processed_filter === 'unprocessed' && !empty($processed_ids)) {
572 $args['post__not_in'] = $processed_ids;
573 }
574
575 // Run the query
576 $query = new WP_Query($args);
577 $content_items = array();
578
579 if ($query->have_posts()) {
580 while ($query->have_posts()) {
581 $query->the_post();
582 $id = get_the_ID();
583 $post_date = get_the_date();
584 $excerpt = wp_trim_words(get_the_excerpt(), 20, '...');
585 $word_count = str_word_count(strip_tags(get_the_content()));
586
587 $is_processed = in_array($id, $processed_ids);
588 $processed_date = '';
589 $db_record_id = 0;
590 $data_source = 'none';
591
592 if ($is_processed && isset($processed_data[$id])) {
593 $item_data = $processed_data[$id];
594 $data_source = $item_data['source'];
595
596 if ($data_source === 'wordpress' && isset($item_data['timestamp'])) {
597 // WordPress DB format
598 $timestamp = strtotime($item_data['timestamp']);
599 $processed_date = human_time_diff($timestamp, current_time('timestamp')) . ' ago';
600 $db_record_id = $item_data['db_id'];
601 } elseif ($data_source === 'pinecone') {
602 // Pinecone format
603 $processed_date = $item_data['processed_date'];
604 $db_record_id = $item_data['db_id'];
605 }
606 }
607
608 $content_items[] = array(
609 'id' => $id,
610 'title' => get_the_title(),
611 'permalink' => get_permalink(),
612 'date' => $post_date,
613 'type' => get_post_type(),
614 'status' => get_post_status(),
615 'excerpt' => $excerpt,
616 'word_count' => $word_count,
617 'already_processed' => $is_processed,
618 'processed_date' => $processed_date,
619 'db_record_id' => $db_record_id,
620 'data_source' => $data_source
621 );
622 }
623 wp_reset_postdata();
624 }
625
626 $response = array(
627 'items' => $content_items,
628 'total' => $query->found_posts,
629 'total_pages' => $query->max_num_pages,
630 'current_page' => $page,
631 'processed_count' => count($processed_ids)
632 );
633
634 wp_send_json_success($response);
635 exit;
636 }
637
638 private function get_public_post_types() {
639 $post_types = get_post_types(array('public' => true), 'objects');
640 $post_type_options = array();
641
642 foreach ($post_types as $post_type) {
643 $post_type_options[$post_type->name] = $post_type->label;
644 }
645
646 return $post_type_options;
647 }
648
649
650 /**
651 * Validates AJAX request nonce for chat content processing
652 */
653 public function ajax_mxchat_process_selected_content() {
654 // Basic request validation
655 if (!check_ajax_referer('mxchat_content_selector_nonce', 'nonce', false)) {
656 wp_send_json_error('Invalid nonce');
657 exit;
658 }
659
660 if (!current_user_can('manage_options')) {
661 wp_send_json_error('Unauthorized access');
662 exit;
663 }
664
665 // Get post IDs - safely parse the array
666 $post_ids = array();
667 if (isset($_POST['post_ids']) && is_array($_POST['post_ids'])) {
668 foreach ($_POST['post_ids'] as $id) {
669 $post_ids[] = absint($id);
670 }
671 }
672
673 if (empty($post_ids)) {
674 wp_send_json_error('No content selected');
675 exit;
676 }
677
678 // Process only ONE post at a time to avoid request size issues
679 $post_id = reset($post_ids);
680 $post = get_post($post_id);
681
682 if (!$post) {
683 wp_send_json_error('Post not found');
684 exit;
685 }
686
687 // Get minimal content
688 $content = $post->post_title . "\n\n" . wp_strip_all_tags($post->post_content);
689 $content = substr($content, 0, 10000); // Limit content size
690
691 // Get API key with proper model detection
692 $options = get_option('mxchat_options');
693 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
694
695 if (strpos($selected_model, 'voyage') === 0) {
696 $api_key = $options['voyage_api_key'] ?? '';
697 $provider_name = 'Voyage AI';
698 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
699 $api_key = $options['gemini_api_key'] ?? '';
700 $provider_name = 'Google Gemini';
701 } else {
702 $api_key = $options['api_key'] ?? '';
703 $provider_name = 'OpenAI';
704 }
705
706 if (empty($api_key)) {
707 wp_send_json_error($provider_name . ' API key not configured');
708 exit;
709 }
710
711 $source_url = get_permalink($post_id);
712 $vector_id = md5($source_url); // Vector ID for Pinecone
713
714 // ================================
715 // UPDATED: Check for existing content in BOTH sources for backwards compatibility
716 // ================================
717
718 $is_update = false;
719
720 // Check WordPress DB first (backwards compatibility)
721 global $wpdb;
722 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
723 $existing_record = $wpdb->get_row($wpdb->prepare(
724 "SELECT id FROM $table_name WHERE source_url = %s",
725 $source_url
726 ));
727
728 if ($existing_record) {
729 $is_update = true;
730 } else {
731 // Also check Pinecone if enabled
732 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
733 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
734
735 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
736 // UPDATED: Use lazy-loaded Pinecone manager
737 $pinecone_manager = $this->get_pinecone_manager();
738 if ($pinecone_manager) {
739 $pinecone_data = $pinecone_manager->get_pinecone_processed_content($pinecone_options);
740 if (isset($pinecone_data[$post_id])) {
741 $is_update = true;
742 }
743 }
744 }
745 }
746
747 // Use the centralized utility function for storage
748 $result = MxChat_Utils::submit_content_to_db(
749 $content,
750 $source_url,
751 $api_key,
752 $vector_id
753 );
754
755 if (is_wp_error($result)) {
756 wp_send_json_error('Storage failed: ' . $result->get_error_message());
757 exit;
758 }
759
760 // ================================
761 // NEW: Update caches immediately after successful storage
762 // ================================
763
764 // Check if Pinecone is enabled and update caches
765 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
766 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
767
768 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
769 // UPDATED: Use lazy-loaded Pinecone manager for vector cache update
770 $pinecone_manager = $this->get_pinecone_manager();
771 if ($pinecone_manager) {
772 $pinecone_manager->update_pinecone_vector_cache($vector_id);
773 }
774
775 // Update local processed content cache for immediate UI feedback
776 $pinecone_cache = get_option('mxchat_pinecone_processed_cache', array());
777 $pinecone_cache[$post_id] = array(
778 'db_id' => $vector_id,
779 'processed_date' => 'Just now',
780 'url' => $source_url,
781 'source' => 'pinecone',
782 'timestamp' => current_time('timestamp')
783 );
784 update_option('mxchat_pinecone_processed_cache', $pinecone_cache);
785
786 // Also update the general processed content cache
787 $processed_cache = get_option('mxchat_processed_content_cache', array());
788 $processed_cache[$post_id] = array(
789 'db_id' => $vector_id,
790 'timestamp' => current_time('timestamp'),
791 'url' => $source_url,
792 'source' => 'pinecone'
793 );
794 update_option('mxchat_processed_content_cache', $processed_cache);
795 }
796
797 $operation_type = $is_update ? 'update' : 'new';
798
799 // Success response with minimal data
800 wp_send_json_success(array(
801 'message' => $operation_type === 'update' ? 'Content updated successfully' : 'Content processed successfully',
802 'post_id' => $post_id,
803 'title' => $post->post_title,
804 'operation_type' => $operation_type,
805 'vector_id' => $vector_id, // Include vector ID for debugging
806 'cache_updated' => $use_pinecone // Indicate if cache was updated
807 ));
808 exit;
809 }
810
811
812 /**
813 * Handles AJAX request to save chat prompt
814 */
815 public function mxchat_save_inline_prompt() {
816 // Check for nonce security
817 check_ajax_referer('mxchat_save_inline_nonce');
818
819 // Verify permissions
820 if (!current_user_can('manage_options')) {
821 wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
822 return;
823 }
824
825 global $wpdb;
826 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
827
828 // Validate and sanitize input data
829 $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
830 $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
831 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
832
833 if ($prompt_id > 0 && !empty($article_content)) {
834 // Re-generate the embedding vector for the updated content
835 $embedding_vector = $this->mxchat_generate_embedding($article_content);
836
837 if (is_array($embedding_vector)) {
838 // Serialize the embedding vector before storing it
839 $embedding_vector_serialized = serialize($embedding_vector);
840
841 // Update the prompt in the database
842 $updated = $wpdb->update(
843 $table_name,
844 array(
845 'article_content' => $article_content,
846 'embedding_vector' => $embedding_vector_serialized,
847 'source_url' => $article_url,
848 ),
849 array('id' => $prompt_id),
850 array('%s', '%s', '%s'),
851 array('%d')
852 );
853
854 if ($updated !== false) {
855 wp_send_json_success();
856 } else {
857 wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
858 }
859 } else {
860 wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
861 }
862 } else {
863 wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
864 }
865 }
866
867
868 // ========================================
869 // STATUS UPDATES AJAX HANDLERS
870 // ========================================
871
872
873 // ========================================
874 // LICENSE AJAX HANDLERS
875 // ========================================
876
877 /**
878 * Validates and activates chat license via AJAX
879 */
880 public function mxchat_handle_activate_license() {
881 // Check nonce
882 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
883 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
884 return;
885 }
886
887 // Verify user capabilities
888 if (!current_user_can('manage_options')) {
889 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
890 return;
891 }
892
893 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
894 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
895
896 if (empty($license_key) || empty($customer_email)) {
897 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
898 return;
899 }
900
901 $product_id = 'MxChatPRO';
902
903 // **CHANGED TO HTTPS**
904 $response = wp_remote_get(
905 add_query_arg(
906 array(
907 'wc-api' => 'software-api',
908 'request' => 'activation',
909 'email' => $customer_email,
910 'license_key' => $license_key,
911 'product_id' => $product_id
912 ),
913 'https://mxchat.ai/' // **CHANGED FROM HTTP TO HTTPS**
914 ),
915 array(
916 'timeout' => 60, // 60 seconds timeout
917 'sslverify' => true // **CHANGED TO TRUE FOR HTTPS**
918 )
919 );
920
921 if (is_wp_error($response)) {
922 $error_message = $response->get_error_message();
923 //error_log('MxChat License Activation Error: ' . $error_message);
924 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $error_message);
925 return;
926 }
927
928 $response_code = wp_remote_retrieve_response_code($response);
929 $body = wp_remote_retrieve_body($response);
930
931 // Log response for debugging
932 //error_log('MxChat License Response Code: ' . $response_code);
933 //error_log('MxChat License Response Body: ' . $body);
934
935 if ($response_code !== 200) {
936 wp_send_json_error(esc_html__('Server returned error code: ', 'mxchat') . $response_code);
937 return;
938 }
939
940 $data = json_decode($body);
941
942 if ($data && isset($data->activated) && $data->activated) {
943 update_option('mxchat_license_status', 'active');
944 update_option('mxchat_pro_email', $customer_email);
945 update_option('mxchat_activation_key', $license_key);
946 delete_option('mxchat_license_error'); // Clear any previous errors
947 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
948 } else {
949 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
950 update_option('mxchat_license_status', 'inactive');
951 update_option('mxchat_license_error', $error_message);
952 wp_send_json_error($error_message);
953 }
954 }
955
956
957 /**
958 * Validates license via AJAX with email and key
959 */
960 public function mxchat_check_license_status() {
961 // Verify nonce
962 check_ajax_referer($this->get_nonce_action(), 'security');
963
964 $email = sanitize_email($_POST['email']);
965 $key = sanitize_text_field($_POST['key']);
966
967 // Check if this license is actually active in your system
968 $is_active = (get_option('mxchat_license_status') === 'active' &&
969 get_option('mxchat_pro_email') === $email &&
970 get_option('mxchat_activation_key') === $key);
971
972 wp_send_json(array(
973 'is_active' => $is_active
974 ));
975 }
976
977
978 // ========================================
979 // ACTIONS & INTENTS AJAX HANDLERS
980 // ========================================
981
982 /**
983 * Validates nonce and returns JSON error on failure
984 */
985 public function mxchat_toggle_action() {
986 // Check nonce
987 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
988 wp_send_json_error(array('message' => 'Security check failed'));
989 return;
990 }
991
992 // Check permissions
993 if (!current_user_can('manage_options')) {
994 wp_send_json_error(array('message' => 'Permission denied'));
995 return;
996 }
997
998 // Validate params
999 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
1000 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
1001
1002 if (!$intent_id) {
1003 wp_send_json_error(array('message' => 'Invalid action ID'));
1004 return;
1005 }
1006
1007 // Update the intent/action status in the database
1008 global $wpdb;
1009 $table_name = $wpdb->prefix . 'mxchat_intents';
1010
1011 // Using the 'enabled' field - add this field if it doesn't exist
1012 $result = $wpdb->update(
1013 $table_name,
1014 array('enabled' => $enabled ? 1 : 0),
1015 array('id' => $intent_id),
1016 array('%d'),
1017 array('%d')
1018 );
1019
1020 if ($result === false) {
1021 wp_send_json_error(array('message' => 'Database error'));
1022 return;
1023 }
1024
1025 wp_send_json_success();
1026 }
1027
1028
1029 /**
1030 * Validates permissions for AJAX request handling
1031 */
1032 public function mxchat_update_intent_threshold() {
1033 // Check permissions
1034 if (!current_user_can('manage_options')) {
1035 if (wp_doing_ajax()) {
1036 wp_send_json_error(array('message' => 'Unauthorized user'));
1037 return;
1038 }
1039 wp_die(esc_html__('Unauthorized user', 'mxchat'));
1040 }
1041
1042 // Verify nonce
1043 check_admin_referer('mxchat_update_intent_threshold_nonce');
1044
1045 // Process the update if we have valid data
1046 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
1047 global $wpdb;
1048 $table_name = $wpdb->prefix . 'mxchat_intents';
1049 $intent_id = intval($_POST['intent_id']);
1050 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
1051 $similarity_threshold = $threshold_percentage / 100;
1052
1053 $result = $wpdb->update(
1054 $table_name,
1055 ['similarity_threshold' => $similarity_threshold],
1056 ['id' => $intent_id],
1057 ['%f'],
1058 ['%d']
1059 );
1060
1061 // Handle AJAX requests
1062 if (wp_doing_ajax()) {
1063 if ($result === false) {
1064 wp_send_json_error(array('message' => 'Failed to update threshold'));
1065 } else {
1066 wp_send_json_success(array('threshold' => $threshold_percentage));
1067 }
1068 return;
1069 }
1070 }
1071
1072 // Redirect for regular form submissions
1073 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
1074 exit;
1075 }
1076
1077 // ========================================
1078 // HELPER METHODS
1079 // ========================================
1080
1081 /**
1082 * Returns a specific nonce action string
1083 */
1084 private function get_nonce_action() {
1085 return 'mxchat_license_nonce';
1086 }
1087
1088 /**
1089 * Checks if a value has changed from old to new
1090 */
1091 private function has_value_changed($old_value, $new_value) {
1092 // Handle null values
1093 if ($old_value === null && $new_value === '') {
1094 return false;
1095 }
1096
1097 // Handle array values (like additional_popular_questions)
1098 if (is_array($old_value) && is_array($new_value)) {
1099 // Convert both to JSON for comparison to handle ordering differences
1100 return json_encode($old_value) !== json_encode($new_value);
1101 }
1102
1103 // Handle toggle/checkbox values consistently
1104 if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {
1105 return false;
1106 }
1107 if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {
1108 return false;
1109 }
1110
1111 // Default direct comparison
1112 return $old_value !== $new_value;
1113 }
1114
1115 }
1116
1117 // Initialize the AJAX handler
1118 new MxChat_Ajax_Handler();
1119