PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.6
MxChat – AI Chatbot & Content Generation for WordPress v2.1.6
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 / includes / class-mxchat-admin.php

class-mxchat-admin.php in MxChat – AI Chatbot & Content Generation for WordPress 2.1.6, at includes/class-mxchat-admin.php

7,871 lines 336.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit; // Exit if accessed directly
4 }
5
6 class MxChat_Admin {
7 private $options;
8 private $chat_count;
9 private $is_activated;
10
11 public function __construct() {
12 $this->options = get_option('mxchat_options');
13 $this->chat_count = get_option('mxchat_chat_count', 0);
14 $this->is_activated = $this->is_license_active();
15
16 // Initialize default options if they are not set
17 if (!$this->options) {
18 $this->initialize_default_options();
19 }
20
21 // Add admin menu and initialize settings
22 add_action('admin_menu', array($this, 'mxchat_add_plugin_page'));
23 add_action('admin_init', array($this, 'mxchat_page_init'));
24 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
25 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
26 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
27 add_action('admin_post_mxchat_submit_content', array($this, 'mxchat_handle_content_submission'));
28 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
29 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
30 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
31 add_action('admin_post_mxchat_submit_sitemap', array($this, 'mxchat_handle_sitemap_submission'));
32 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
33 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
34 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
35 add_action('wp_ajax_mxchat_activate_license', array($this, 'mxchat_handle_activate_license'));
36 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
37 add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt'));
38 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
39 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
40 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
41 add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action'));
42 add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold'));
43 add_action('admin_post_mxchat_stop_processing', array($this, 'mxchat_stop_processing'));
44 add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
45 add_action('save_post', array($this, 'handle_post_update'), 10, 3);
46 add_action('post_updated', array($this, 'handle_post_update'), 10, 3);
47 add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback'));
48 add_action('wp_trash_post', array($this, 'mxchat_handle_post_delete'));
49 add_action('before_delete_post', array($this, 'mxchat_handle_post_delete'));
50 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
51 add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback'));
52 if (isset($this->options['enable_woocommerce_integration']) &&
53 ($this->options['enable_woocommerce_integration'] === '1' ||
54 $this->options['enable_woocommerce_integration'] === 'on')) {
55
56 //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks');
57 add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3);
58 add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete'));
59 add_action('before_delete_post', array($this, 'mxchat_handle_product_delete'));
60 }
61
62 add_action('wp_ajax_mxchat_get_status_updates', array($this, 'ajax_get_status_updates'));
63 add_action('admin_notices', array($this, 'display_admin_notices'));
64
65 }
66
67 // Method to check if the license is active
68 private function is_license_active() {
69 $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat'));
70 return $license_status === esc_html__('active', 'mxchat');
71 }
72
73 // Initialize default options
74 private function initialize_default_options() {
75 $default_options = array(
76 'api_key' => '',
77 'xai_api_key' => '',
78 'claude_api_key' => '',
79 'deepseek_api_key' => '',
80 'voyage_api_key' => '',
81 'gemini_api_key' => '',
82 'embedding_model' => 'text-embedding-ada-002',
83 'system_prompt_instructions' => esc_html__('[EXAMPLE INSTRUCTIONS] You are an AI Chatbot assistant for this website. The primary subject you should focus on is [insert proper subject here]. Your main goal is to assist visitors with questions related to this specific topic. Here are some key things to keep in mind:
84 - Your name is [Chatbot Name].
85 - Stay focused on topics related to [insert proper subject here]. If a visitor asks about an unrelated topic, politely redirect the conversation to how you can assist them with this subject.
86 - Do not make up links to pages. Only use specific links you see in your knowledgebase from [insert your domain]. Do not make assumptions or make up URLs.
87 - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction.
88 - Provide answers based on the knowledge available to you. If you do not have an answer to a specific question, let the visitor know that you don’t have the information.', 'mxchat'),
89 'model' => esc_html__('gpt-4o', 'mxchat'),
90 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
91 'role_rate_limits' => array(),
92 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
93 'enable_email_block' => '',
94 'email_blocker_header_content' => __("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
95 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
96 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
97 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
98 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
99 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
100 'append_to_body' => esc_html__('off', 'mxchat'),
101 'close_button_color' => esc_html__('#fff', 'mxchat'),
102 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
103 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
104 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
105 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'),
106 'bot_message_font_color' => esc_html__('#fff', 'mxchat'),
107 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'),
108 'send_button_font_color' => esc_html__('#212121', 'mxchat'),
109 'chat_input_font_color' => esc_html__('#212121', 'mxchat'),
110 'chatbot_background_color' => esc_html__('#212121', 'mxchat'),
111 'icon_color' => esc_html__('#fff', 'mxchat'),
112 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'),
113 'link_target_toggle' => esc_html__('off', 'mxchat'),
114 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'),
115
116 // New fields for Loops Integration
117 'loops_api_key' => '',
118 'loops_mailing_list' => '',
119 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
120 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
121 'popular_question_1' => '',
122 'popular_question_2' => '',
123 'popular_question_3' => '',
124 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
125 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
126 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
127 'pdf_max_pages' => 69,
128 'show_pdf_upload_button' => 'on',
129 'show_word_upload_button' => 'on',
130
131 // Live Agent Integration
132 'live_agent_webhook_url' => '',
133 'live_agent_secret_key' => '',
134 'live_agent_bot_token' => '',
135 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
136 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
137 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
138 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
139 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
140 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
141 );
142
143
144 // Merge existing options with defaults
145 $existing_options = get_option('mxchat_options', array());
146 $merged_options = wp_parse_args($existing_options, $default_options);
147
148 // Update the options if they have changed
149 if ($existing_options !== $merged_options) {
150 update_option('mxchat_options', $merged_options);
151 }
152
153 // Add default limits for each role
154 $roles = wp_roles()->get_names();
155 foreach ($roles as $role_id => $role_name) {
156 $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat');
157 }
158
159 return $default_options;
160
161 // Update the $this->options property
162 $this->options = $merged_options;
163 }
164
165
166
167 public function mxchat_add_plugin_page() {
168 // Main menu page
169 add_menu_page(
170 esc_html__('MxChat Settings', 'mxchat'),
171 esc_html__('MxChat', 'mxchat'),
172 'manage_options',
173 'mxchat-max',
174 array($this, 'mxchat_create_admin_page'),
175 'dashicons-testimonial',
176 6
177 );
178
179 // Submenu page for Knowledge
180 add_submenu_page(
181 'mxchat-max',
182 esc_html__('Prompts', 'mxchat'),
183 esc_html__('Knowledge', 'mxchat'),
184 'manage_options',
185 'mxchat-prompts',
186 array($this, 'mxchat_create_prompts_page')
187 );
188
189 add_submenu_page(
190 'mxchat-max',
191 esc_html__('Chat Transcripts', 'mxchat'),
192 esc_html__('Transcripts', 'mxchat'),
193 'manage_options',
194 'mxchat-transcripts',
195 array($this, 'mxchat_create_transcripts_page')
196 );
197
198 add_submenu_page(
199 'mxchat-max',
200 esc_html__('MxChat Actions', 'mxchat'),
201 esc_html__('Actions', 'mxchat'),
202 'manage_options',
203 'mxchat-actions',
204 array($this, 'mxchat_actions_page_html')
205 );
206
207 add_submenu_page(
208 'mxchat-max',
209 esc_html__('Add Ons', 'mxchat'),
210 esc_html__('Add Ons', 'mxchat'),
211 'manage_options',
212 'mxchat-addons',
213 array($this, 'mxchat_create_addons_page')
214 );
215
216 // Submenu page for Activation Key
217 add_submenu_page(
218 'mxchat-max',
219 esc_html__('Pro Upgrade', 'mxchat'),
220 esc_html__('Pro Upgrade', 'mxchat'),
221 'manage_options',
222 'mxchat-activation',
223 array($this, 'mxchat_create_activation_page')
224 );
225 }
226
227 public function mxchat_create_addons_page() {
228 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
229 $addons_page = new MxChat_Addons();
230 $addons_page->render_page();
231 }
232
233 public function mxchat_save_setting_callback() {
234 check_ajax_referer('mxchat_save_setting_nonce');
235 if (!current_user_can('manage_options')) {
236 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
237 }
238
239 $name = isset($_POST['name']) ? $_POST['name'] : '';
240 // Strip slashes from the value before saving
241 $value = isset($_POST['value']) ? stripslashes($_POST['value']) : '';
242
243 if (empty($name)) {
244 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
245 }
246
247 // Load the full options array
248 $options = get_option('mxchat_options', []);
249
250 // Handle special cases
251 switch ($name) {
252 case 'additional_popular_questions':
253 $questions = json_decode($value, true); // No need for stripslashes here
254 if (is_array($questions)) {
255 $options[$name] = $questions;
256 // Also update old option for backwards compatibility
257 update_option('additional_popular_questions', $questions);
258 }
259 break;
260 case 'email_blocker_header_content':
261 // Allow HTML content but sanitize it safely
262 $options[$name] = wp_kses_post($value);
263 break;
264 case 'similarity_threshold':
265 // Save to the options array
266 $options[$name] = $value;
267 break;
268 case 'user_message_bg_color':
269 case 'user_message_font_color':
270 case 'bot_message_bg_color':
271 case 'bot_message_font_color':
272 case 'top_bar_bg_color':
273 case 'send_button_font_color':
274 case 'chatbot_background_color':
275 case 'icon_color':
276 case 'chat_input_font_color':
277 case 'live_agent_message_bg_color':
278 case 'live_agent_message_font_color':
279 case 'mode_indicator_bg_color':
280 case 'mode_indicator_font_color':
281 case 'toolbar_icon_color':
282 // Store color values directly
283 $options[$name] = $value;
284 break;
285 case 'live_agent_status':
286 // Set the new value
287 $options[$name] = ($value === 'on') ? 'on' : 'off';
288 break;
289 case 'enable_woocommerce_integration':
290 // Handle values that used to be 1/0
291 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off';
292 break;
293 default:
294 // Handle role rate limits
295 if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) {
296 // Extract role ID from the name
297 preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches);
298 if (isset($matches[1])) {
299 $role_id = $matches[1];
300 // Initialize role_rate_limits if it doesn't exist
301 if (!isset($options['role_rate_limits'])) {
302 $options['role_rate_limits'] = [];
303 }
304 // Update the specific role's rate limit
305 $options['role_rate_limits'][$role_id] = sanitize_text_field($value);
306 break;
307 }
308 }
309 // Handle toggles
310 if (strpos($name, 'toggle') !== false || in_array($name, [
311 'chat_persistence_toggle',
312 'privacy_toggle',
313 'complianz_toggle',
314 'chat_toolbar_toggle',
315 'show_pdf_upload_button',
316 'show_word_upload_button'
317 ])) {
318 $options[$name] = ($value === 'on') ? 'on' : 'off';
319 } else {
320 // Store all other values directly
321 $options[$name] = $value;
322 }
323 break;
324 }
325
326 // Save all updates to the options array
327 $updated = update_option('mxchat_options', $options);
328
329 // Always return success even if WordPress says nothing changed
330 // (which happens when the value is the same as before)
331 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
332 }
333
334
335 /**
336 * Helper function to compare if a value has changed
337 * Handles various data types appropriately
338 */
339 private function has_value_changed($old_value, $new_value) {
340 // Handle null values
341 if ($old_value === null && $new_value === '') {
342 return false;
343 }
344
345 // Handle array values (like additional_popular_questions)
346 if (is_array($old_value) && is_array($new_value)) {
347 // Convert both to JSON for comparison to handle ordering differences
348 return json_encode($old_value) !== json_encode($new_value);
349 }
350
351 // Handle toggle/checkbox values consistently
352 if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) {
353 return false;
354 }
355 if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) {
356 return false;
357 }
358
359 // Default direct comparison
360 return $old_value !== $new_value;
361 }
362
363 /**
364 * Handles AJAX auto-save for prompts and auto-sync settings.
365 */
366 public function mxchat_save_prompts_setting_callback() {
367 check_ajax_referer('mxchat_prompts_setting_nonce', '_ajax_nonce');
368
369 if (!current_user_can('manage_options')) {
370 wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]);
371 }
372
373 $name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : '';
374 $value = isset($_POST['value']) ? sanitize_text_field($_POST['value']) : '';
375
376 if (empty($name)) {
377 wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]);
378 }
379
380 // Log the values we're trying to save (for debugging)
381 //error_log('Attempting to save setting: ' . $name . ' = ' . $value);
382
383 // For all auto-sync options
384 if (strpos($name, 'mxchat_auto_sync_') === 0) {
385 // Convert 'on'/'off' to '1'/'0' for consistency (if needed)
386 $option_value = ($value === 'on') ? '1' : '0';
387
388 $result = update_option($name, $option_value);
389
390 if ($result) {
391 //error_log('Successfully saved setting: ' . $name . ' = ' . $option_value);
392 wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]);
393 } else {
394 //error_log('Failed to save setting: ' . $name . ' (no changes or error)');
395 wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]);
396 }
397 return;
398 }
399
400 // Handle fields stored in the 'mxchat_prompts_options' array.
401 // Handle fields stored in the 'mxchat_prompts_options' array.
402 if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) {
403 // Extract the key name using regex.
404 if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) {
405 $key = sanitize_text_field( $matches[1] );
406 $options = get_option( 'mxchat_prompts_options', [] );
407
408 // Remove Pinecone-specific settings from the main plugin handling.
409 if ( 'mxchat_use_pinecone' === $key ) {
410 // Optionally, you could simply ignore this field or set a default value.
411 // For example, ensure it's always 0 or remove it entirely:
412 // $options[ $key ] = '0';
413 // Or, if you prefer, just return an error so that the main plugin doesn't handle it:
414 wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] );
415 } else {
416 // For all other fields, use the existing logic.
417 $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value;
418 }
419 update_option( 'mxchat_prompts_options', $options );
420 wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] );
421 }
422 }
423
424
425 wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] );
426 }
427 public function mxchat_display_admin_notice() {
428 // Success notice
429 if ($message = get_transient('mxchat_admin_notice_success')) {
430 ?>
431 <div class="notice notice-success is-dismissible">
432 <p><?php echo esc_html($message); ?></p>
433 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
434 </div>
435 <?php
436 delete_transient('mxchat_admin_notice_success'); // Clear the transient after displaying
437 }
438
439 // Error notice
440 if ($message = get_transient('mxchat_admin_notice_error')) {
441 ?>
442 <div class="notice notice-error is-dismissible">
443 <p><?php echo esc_html($message); ?></p>
444 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
445 </div>
446 <?php
447 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
448 }
449 }
450
451
452
453
454
455 public function mxchat_create_admin_page() {
456
457 ?>
458 <div class="wrap mxchat-wrapper">
459 <!-- Hero Section -->
460 <div class="mxchat-hero">
461 <h1 class="mxchat-main-title">
462 <span class="mxchat-gradient-text">MxChat</span> Settings
463 </h1>
464 <p class="mxchat-hero-subtitle">
465 <?php esc_html_e('Configure your AI chatbot, manage integrations and explore tutorials to get the most out of MxChat.', 'mxchat'); ?>
466 </p>
467 </div>
468
469 <div class="mxchat-content">
470 <?php if (!$this->is_activated): ?>
471 <div class="mxchat-pro-card">
472 <div class="mxchat-pro-notification">
473 <div class="mxchat-pro-content">
474 <h3>🚀 Limited Time Offer: Save $20 on MxChat Pro Lifetime Access!</h3>
475 <p>Unlock <strong>unlimited access</strong> to our growing collection of powerful add-ons including Forms Builder, Theme Customizer, WooCommerce, Perplexity, and more all included with your lifetime license!</p>
476 </div>
477 <div class="mxchat-pro-cta">
478 <a href="https://mxchat.ai/" target="_blank" class="mxchat-button"><?php echo esc_html__('Upgrade to Pro Today', 'mxchat'); ?></a>
479 <a href="<?php echo admin_url('admin.php?page=mxchat-addons'); ?>" class="mxchat-link"><?php echo esc_html__('Preview Add-ons', 'mxchat'); ?></a>
480 </div>
481 </div>
482 </div>
483 <?php endif; ?>
484
485 <!-- Tabs Navigation -->
486 <div class="mxchat-tabs">
487 <button class="mxchat-tab-button active" data-tab="chatbot"><?php echo esc_html__('Chatbot', 'mxchat'); ?></button>
488 <button class="mxchat-tab-button" data-tab="embed"><?php echo esc_html__('Integrations', 'mxchat'); ?></button>
489 <button class="mxchat-tab-button" data-tab="general"><?php echo esc_html__('YouTube Tutorials', 'mxchat'); ?></button>
490 </div>
491
492 <!-- Tab Contents -->
493 <div id="chatbot" class="mxchat-tab-content active">
494 <div class="mxchat-card">
495 <div class="mxchat-autosave-section">
496 <?php do_settings_sections('mxchat-chatbot'); ?>
497 </div>
498 </div>
499 </div>
500
501 <div id="embed" class="mxchat-tab-content">
502 <div class="mxchat-card">
503 <h2><?php esc_html_e('Loops Settings', 'mxchat'); ?></h2>
504 <div class="mxchat-autosave-section">
505 <table class="form-table">
506 <?php do_settings_fields('mxchat-embed', 'mxchat_loops_section'); ?>
507 </table>
508 </div>
509 </div>
510
511 <div class="mxchat-card">
512 <h2><?php esc_html_e('Brave Search Settings', 'mxchat'); ?></h2>
513 <div class="mxchat-autosave-section">
514 <table class="form-table">
515 <?php do_settings_fields('mxchat-embed', 'mxchat_brave_section'); ?>
516 </table>
517 </div>
518 </div>
519
520 <div class="mxchat-card">
521 <h2><?php esc_html_e('Toolbar Settings & Intents', 'mxchat'); ?></h2>
522 <div class="mxchat-autosave-section">
523 <table class="form-table">
524 <?php do_settings_fields('mxchat-embed', 'mxchat_pdf_intent_section'); ?>
525 </table>
526 </div>
527 </div>
528
529 <div class="mxchat-card">
530 <h2><?php esc_html_e('Live Agent Settings', 'mxchat'); ?></h2>
531 <div class="mxchat-autosave-section">
532 <p><?php echo esc_html__('Visit our', 'mxchat'); ?> <a href="https://mxchat.ai/documentation/#slack_integration" target="_blank"><?php echo esc_html__('documentation page', 'mxchat'); ?></a> <?php echo esc_html__('to set up live agent transfer via Slack.', 'mxchat'); ?></p>
533 <table class="form-table">
534 <?php do_settings_fields('mxchat-embed', 'mxchat_live_agent_section'); ?>
535 </table>
536 </div>
537 </div>
538 </div>
539
540 <div id="general" class="mxchat-tab-content">
541 <div class="mxchat-card">
542 <?php do_settings_sections('mxchat-general'); ?>
543 <div class="video-tutorials-section">
544
545 <div class="tutorial-grid">
546 <div class="tutorial-item">
547 <h3><?php echo esc_html__('MxChat Forms Tutorial', 'mxchat'); ?></h3>
548 <div class="video-description">
549 <p><?php echo esc_html__('Learn how to create and manage smart forms that automatically trigger during chat conversations.', 'mxchat'); ?></p>
550 <a href="https://www.youtube.com/watch?v=3MrWy5dRalA" target="_blank" rel="noopener" class="video-link">
551 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
552 <?php echo esc_html__('Watch MxChat Forms Tutorial', 'mxchat'); ?>
553 </a>
554 </div>
555 </div>
556
557 <div class="tutorial-item">
558 <h3><?php echo esc_html__('Intent Tester Guide', 'mxchat'); ?></h3>
559 <div class="video-description">
560 <p><?php echo esc_html__('Discover how to use the Intent Tester to fine-tune your chatbot\'s responses and ensure it accurately understands user queries.', 'mxchat'); ?></p>
561 <a href="https://www.youtube.com/watch?v=uTr14tn59Hc" target="_blank" rel="noopener" class="video-link">
562 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
563 <?php echo esc_html__('Watch Intent Tester Tutorial', 'mxchat'); ?>
564 </a>
565 </div>
566 </div>
567
568 <div class="tutorial-item">
569 <h3><?php echo esc_html__('WooCommerce Integration', 'mxchat'); ?></h3>
570 <div class="video-description">
571 <p><?php echo esc_html__('See how to integrate MxChat with your WooCommerce store to provide product recommendations and shopping assistance to your customers.', 'mxchat'); ?></p>
572 <a href="https://www.youtube.com/watch?v=WsqAppHRGdA" target="_blank" rel="noopener" class="video-link">
573 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
574 <?php echo esc_html__('Watch WooCommerce Integration Tutorial', 'mxchat'); ?>
575 </a>
576 </div>
577 </div>
578
579 <div class="tutorial-item">
580 <h3><?php echo esc_html__('Knowledge Base Setup', 'mxchat'); ?></h3>
581 <div class="video-description">
582 <p><?php echo esc_html__('Learn how to set up your knowledge base using PDFs, sitemaps, and manual entries to enhance your chatbot\'s responses with site-specific information.', 'mxchat'); ?></p>
583 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
584 <a href="https://www.youtube.com/watch?v=8Ztjs66-VTo" target="_blank" rel="noopener" class="video-link">
585 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
586 <?php echo esc_html__('Watch Knowledge Base Setup Tutorial', 'mxchat'); ?>
587 </a>
588 </div>
589 </div>
590
591 <div class="tutorial-item">
592 <h3><?php echo esc_html__('Toolbar Chat with Documents', 'mxchat'); ?></h3>
593 <div class="video-description">
594 <p><?php echo esc_html__('See how to use the MxChat toolbar to chat with PDF and Word documents for enhanced document analysis and information retrieval.', 'mxchat'); ?></p>
595 <a href="https://www.youtube.com/watch?v=j_c45WWCTG0" target="_blank" rel="noopener" class="video-link">
596 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
597 <?php echo esc_html__('Watch Document Chat Tutorial', 'mxchat'); ?>
598 </a>
599 </div>
600 </div>
601
602 <div class="tutorial-item">
603 <h3><?php echo esc_html__('Perplexity Integration', 'mxchat'); ?></h3>
604 <div class="video-description">
605 <p><?php echo esc_html__('Learn how to integrate Perplexity with your chatbot for real-time web search capabilities. This tutorial covers intent recognition, the toolbar toggle button, and how to enable your chatbot to search the web and provide up-to-date information to your visitors.', 'mxchat'); ?></p>
606 <a href="https://youtu.be/wpKkbt24-bo" target="_blank" rel="noopener" class="video-link">
607 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
608 <?php echo esc_html__('Watch Perplexity Integration Tutorial', 'mxchat'); ?>
609 </a>
610 </div>
611 </div>
612
613 <div class="tutorial-item">
614 <h3><?php echo esc_html__('Brave Search Intent', 'mxchat'); ?></h3>
615 <div class="video-description">
616 <p><?php echo esc_html__('Learn how to leverage Brave Search intent capabilities to improve your chatbot\'s understanding of user queries and provide more accurate responses.', 'mxchat'); ?></p>
617 <a href="https://www.youtube.com/watch?v=7vDL5H7vToc" target="_blank" rel="noopener" class="video-link">
618 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
619 <?php echo esc_html__('Watch Brave Search Intent Tutorial', 'mxchat'); ?>
620 </a>
621 </div>
622 </div>
623
624 <div class="tutorial-item">
625 <h3><?php echo esc_html__('Loops Email Capture', 'mxchat'); ?></h3>
626 <div class="video-description">
627 <p><?php echo esc_html__('Discover how to set up email capture with MxChat using Loops to grow your mailing list while providing value through your chatbot.', 'mxchat'); ?></p>
628 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
629 <a href="https://www.youtube.com/watch?v=CNgm5TYDyTc" target="_blank" rel="noopener" class="video-link">
630 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
631 <?php echo esc_html__('Watch Loops Email Capture Tutorial', 'mxchat'); ?>
632 </a>
633 </div>
634 </div>
635
636 <div class="tutorial-item">
637 <h3><?php echo esc_html__('MxChat AI Agent Testing Service', 'mxchat'); ?></h3>
638 <div class="video-description">
639 <p><?php echo esc_html__('Learn how to use the MxChat AI Agent Testing Service to evaluate and improve your chatbot\'s performance and accuracy.', 'mxchat'); ?></p>
640 <p><small><?php echo esc_html__('Note: This tutorial uses an older UI, but the process remains the same.', 'mxchat'); ?></small></p>
641 <a href="https://www.youtube.com/watch?v=A0jowbpyX54" target="_blank" rel="noopener" class="video-link">
642 <span class="video-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6z"></path><polygon points="10 15 15 12 10 9 10 15"></polygon></svg></span>
643 <?php echo esc_html__('Watch AI Agent Testing Tutorial', 'mxchat'); ?>
644 </a>
645 </div>
646 </div>
647 </div>
648
649 <div class="support-section">
650 <h3><?php echo esc_html__('Need Help?', 'mxchat'); ?></h3>
651 <div class="support-content">
652 <p>
653 <?php echo esc_html__('If you\'re having trouble with setup or getting the responses you need, we encourage you to review our', 'mxchat'); ?>
654 <a href="https://mxchat.ai/documentation/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('documentation', 'mxchat'); ?></a> <?php echo esc_html__('or', 'mxchat'); ?>
655 <a href="https://wordpress.org/support/plugin/mxchat-basic/" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('create a support ticket', 'mxchat'); ?></a>.
656 </p>
657 <p>
658 <?php echo esc_html__('If you like our plugin, please consider', 'mxchat'); ?> <a href="https://wordpress.org/plugins/mxchat-basic/#reviews" target="_blank" rel="noopener noreferrer"><?php echo esc_html__('leaving us a review', 'mxchat'); ?></a>.
659 </p>
660 </div>
661 </div>
662 </div>
663 </div>
664 </div>
665 </div>
666 </div>
667 <?php
668 }
669
670
671 public function mxchat_create_transcripts_page() {
672 global $wpdb;
673 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
674
675 // Get basic stats
676 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name");
677 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
678
679 // Count unique users with detailed breakdown
680 $total_users = $wpdb->get_var("
681 SELECT COUNT(DISTINCT
682 CASE
683 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
684 WHEN user_id != 0 THEN CONCAT('user_', user_id)
685 WHEN user_identifier NOT LIKE 'Tech-Savvy User'
686 AND user_identifier NOT LIKE 'Detail-Oriented User'
687 AND user_identifier NOT LIKE 'Language Learner'
688 AND user_identifier NOT LIKE 'Casual Browser'
689 AND user_identifier NOT LIKE 'Policy Enforcer'
690 AND user_identifier NOT LIKE 'Researcher'
691 AND user_identifier NOT LIKE 'Loyalty Member'
692 AND user_identifier NOT LIKE 'Gift Buyer'
693 AND user_identifier NOT LIKE 'Parent or Caregiver'
694 THEN user_identifier
695 ELSE session_id
696 END
697 )
698 FROM $table_name
699 WHERE role != 'assistant'
700 ");
701
702 // Get user type breakdown
703 $registered_users = $wpdb->get_var("
704 SELECT COUNT(DISTINCT user_email)
705 FROM $table_name
706 WHERE user_email != '' AND user_email IS NOT NULL
707 ");
708
709 $guest_users = $wpdb->get_var("
710 SELECT COUNT(DISTINCT user_identifier)
711 FROM $table_name
712 WHERE (user_email = '' OR user_email IS NULL)
713 AND role != 'assistant'
714 AND user_identifier NOT LIKE 'Tech-Savvy User'
715 AND user_identifier NOT LIKE 'Detail-Oriented User'
716 AND user_identifier NOT LIKE 'Language Learner'
717 AND user_identifier NOT LIKE 'Casual Browser'
718 AND user_identifier NOT LIKE 'Policy Enforcer'
719 AND user_identifier NOT LIKE 'Researcher'
720 AND user_identifier NOT LIKE 'Loyalty Member'
721 AND user_identifier NOT LIKE 'Gift Buyer'
722 AND user_identifier NOT LIKE 'Parent or Caregiver'
723 ");
724
725 // Get agent test messages count
726 $agent_tests = $wpdb->get_var("
727 SELECT COUNT(DISTINCT session_id)
728 FROM $table_name
729 WHERE user_identifier IN (
730 'Tech-Savvy User',
731 'Detail-Oriented User',
732 'Language Learner',
733 'Casual Browser',
734 'Policy Enforcer',
735 'Researcher',
736 'Loyalty Member',
737 'Gift Buyer',
738 'Parent or Caregiver'
739 )
740 ");
741 ?>
742 <div class="wrap mxchat-transcripts-wrapper">
743 <!-- Hero Section -->
744 <div class="mxchat-transcripts-hero">
745 <h1 class="mxchat-main-title">
746 Chat <span class="mxchat-gradient-text">Transcripts</span>
747 </h1>
748 <p class="mxchat-hero-subtitle">
749 <?php esc_html_e('Review and manage your chatbot conversations with detailed message history.', 'mxchat'); ?>
750 </p>
751 </div>
752 <div class="mxchat-content">
753 <!-- Stats Cards -->
754 <div class="mxchat-stats-grid">
755 <div class="mxchat-stat-card">
756 <div class="stat-icon">💬</div>
757 <div class="stat-content">
758 <span class="stat-value"><?php echo esc_html($total_chats); ?></span>
759 <span class="stat-label"><?php esc_html_e('Total Chats', 'mxchat'); ?></span>
760 </div>
761 </div>
762 <div class="mxchat-stat-card">
763 <div class="stat-icon">📝</div>
764 <div class="stat-content">
765 <span class="stat-value"><?php echo esc_html($total_messages); ?></span>
766 <span class="stat-label"><?php esc_html_e('Total Messages', 'mxchat'); ?></span>
767 </div>
768 </div>
769 <div class="mxchat-stat-card">
770 <div class="stat-icon">👥</div>
771 <div class="stat-content">
772 <span class="stat-value"><?php echo esc_html($total_users); ?></span>
773 <span class="stat-label"><?php esc_html_e('Unique Users', 'mxchat'); ?></span>
774 <span class="stat-sublabel">
775 <?php
776 echo sprintf(
777 esc_html__('%d registered, %d guests, %d agent tests', 'mxchat'),
778 $registered_users,
779 $guest_users,
780 $agent_tests
781 );
782 ?>
783 </span>
784 </div>
785 </div>
786 </div>
787 <!-- Search and Filter Controls -->
788 <div class="mxchat-controls-wrapper">
789 <div class="mxchat-search-box">
790 <input type="text" id="mxchat-search-transcripts"
791 placeholder="<?php esc_attr_e('Search transcripts...', 'mxchat'); ?>"
792 class="regular-text">
793 </div>
794 <form id="mxchat-delete-form" method="post">
795 <?php wp_nonce_field('mxchat_delete_chat_history', 'mxchat_delete_chat_nonce'); ?>
796 <div class="mxchat-controls">
797 <button type="button" id="mxchat-export-transcripts" class="mxchat-action-button">
798 <span class="dashicons dashicons-download"></span>
799 <?php esc_html_e('Export All Chats', 'mxchat'); ?>
800 </button>
801 <button type="button" id="mxchat-select-all-transcripts" class="mxchat-select-button">
802 <span class="dashicons dashicons-yes-alt"></span>
803 <span class="button-text"><?php esc_html_e('Select All', 'mxchat'); ?></span>
804 </button>
805 <button type="submit" class="button delete-chats-button">
806 <span class="dashicons dashicons-trash"></span>
807 <?php esc_html_e('Delete Selected', 'mxchat'); ?>
808 </button>
809 </div>
810 </form>
811 </div>
812 <!-- Transcripts Container -->
813 <div id="mxchat-transcripts"></div>
814 </div>
815 </div>
816 <?php
817 }
818
819 public function export_chat_transcripts() {
820 if (!current_user_can('manage_options')) {
821 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
822 }
823
824 check_ajax_referer('mxchat_export_transcripts', 'security');
825
826 global $wpdb;
827 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
828
829 // Get all transcripts ordered by session and timestamp
830 $results = $wpdb->get_results(
831 "SELECT session_id, user_email, user_identifier, role, message, timestamp
832 FROM {$table_name}
833 ORDER BY session_id, timestamp ASC"
834 );
835
836 if (empty($results)) {
837 wp_send_json_error(array('message' => 'No transcripts found.'));
838 wp_die();
839 }
840
841 // Set headers for CSV download
842 header('Content-Type: text/csv');
843 header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
844 header('Pragma: no-cache');
845 header('Expires: 0');
846
847 // Create output stream
848 $output = fopen('php://output', 'w');
849
850 // Add UTF-8 BOM for proper Excel encoding
851 fputs($output, "\xEF\xBB\xBF");
852
853 // Add CSV headers
854 fputcsv($output, array(
855 'Session ID',
856 'Email',
857 'User Identifier',
858 'Role',
859 'Message',
860 'Timestamp'
861 ));
862
863 // Add data rows
864 foreach ($results as $row) {
865 fputcsv($output, array(
866 $row->session_id,
867 $row->user_email,
868 $row->user_identifier,
869 $row->role,
870 $row->message,
871 $row->timestamp
872 ));
873 }
874
875 fclose($output);
876 wp_die();
877 }
878
879 public function mxchat_fetch_chat_history() {
880 global $wpdb;
881 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
882
883 if (!current_user_can('manage_options')) {
884 wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat'));
885 }
886
887 // First get unique session IDs ordered by most recent message in each session
888 $session_ids = $wpdb->get_col(
889 $wpdb->prepare(
890 "SELECT DISTINCT session_id
891 FROM {$table_name}
892 GROUP BY session_id
893 ORDER BY MAX(timestamp) DESC"
894 )
895 );
896
897 if (empty($session_ids)) {
898 wp_die(esc_html__('No chat history available.', 'mxchat'));
899 }
900
901 ob_start();
902 echo '<div class="mxchat-transcript">';
903
904 // Iterate through sessions from newest to oldest
905 foreach ($session_ids as $session_id) {
906 // Get the email associated with this session (if available)
907 $email = $wpdb->get_var(
908 $wpdb->prepare(
909 "SELECT user_email
910 FROM {$table_name}
911 WHERE session_id = %s AND user_email != ''
912 ORDER BY timestamp ASC
913 LIMIT 1",
914 $session_id
915 )
916 );
917
918 // Debug: Check email retrieval
919 if (empty($email)) {
920 //error_log("No email found for session {$session_id}");
921 } else {
922 //error_log("Email for session {$session_id}: " . $email);
923 }
924
925 // Get messages for this session ordered by timestamp
926 $messages = $wpdb->get_results(
927 $wpdb->prepare(
928 "SELECT * FROM {$table_name}
929 WHERE session_id = %s
930 ORDER BY timestamp ASC",
931 $session_id
932 )
933 );
934
935 // Start session block
936 echo '<div class="mxchat-session">';
937 echo '<div class="mxchat-session-header">';
938 // Wrap checkbox and session ID in one block
939 echo '<div class="mxchat-session-id">';
940 echo '<input type="checkbox" name="delete_session_ids[]" value="' . esc_attr($session_id) . '"> ';
941 echo '<strong>' . esc_html__('Session ID:', 'mxchat') . '</strong> ' . esc_html($session_id);
942 echo '</div>';
943
944 // Place email directly below the session ID block
945 if (!empty($email)) {
946 echo '<div class="mxchat-session-email">';
947 echo '<strong>' . esc_html__('Email:', 'mxchat') . '</strong> ' . esc_html($email);
948 echo '</div>';
949 }
950 echo '</div>';
951
952 echo '<div class="mxchat-messages">';
953
954 // Display messages for this session
955 foreach ($messages as $transcript) {
956 $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp));
957
958 // Determine message styling
959 switch ($transcript->role) {
960 case 'assistant':
961 case 'bot':
962 $message_class = 'bot-message';
963 $display_role = esc_html__('Chatbot', 'mxchat');
964 break;
965 case 'user':
966 $message_class = 'user-message';
967 $display_role = !empty($transcript->user_identifier)
968 ? sanitize_text_field($transcript->user_identifier)
969 : esc_html__('User', 'mxchat');
970 break;
971 case 'agent':
972 $message_class = 'agent-message';
973 $display_role = esc_html__('Agent', 'mxchat');
974 break;
975 default:
976 $message_class = 'unknown-message';
977 $display_role = esc_html__('Unknown', 'mxchat');
978 }
979
980 // Process message content
981 $message_content = wp_kses(
982 stripslashes($transcript->message),
983 [
984 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
985 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
986 'a' => ['href' => [], 'title' => []]
987 ]
988 );
989 $message_content = nl2br($message_content);
990
991 // Render message
992 echo '<div class="mxchat-message ' . esc_attr($message_class) . '">';
993 echo '<div class="mxchat-message-header">' . esc_html($display_role) . '</div>';
994 echo '<div class="mxchat-message-content">' . $message_content . '</div>';
995 echo '<div class="mxchat-timestamp">' . esc_html($formatted_timestamp) . '</div>';
996 echo '</div>';
997 }
998
999 // Close session block
1000 echo '</div></div>';
1001 }
1002
1003 echo '</div>';
1004 $output = ob_get_clean();
1005 echo $output;
1006 wp_die();
1007 }
1008
1009
1010
1011 public function mxchat_create_prompts_page() {
1012 global $wpdb;
1013 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1014
1015 // Display success message if all prompts were deleted
1016 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
1017 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p></div>';
1018 }
1019
1020 // Set up pagination and search query
1021 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
1022 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
1023 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
1024 $per_page = 10;
1025 $offset = ($current_page - 1) * $per_page;
1026
1027 // Modify query to handle search input
1028 $sql_search = "";
1029 if ($search_query) {
1030 $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%');
1031 }
1032
1033 // Retrieve total number of prompts, considering search filter
1034 $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}");
1035 $total_pages = ceil($total_prompts / $per_page);
1036
1037 // Retrieve prompts from the database
1038 $prompts = $wpdb->get_results(
1039 $wpdb->prepare(
1040 "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d",
1041 $per_page,
1042 $offset
1043 )
1044 );
1045
1046 // Add the pagination links generation here
1047 $page_links = paginate_links(array(
1048 'base' => add_query_arg(array(
1049 'paged' => '%#%',
1050 'search' => urlencode($search_query),
1051 '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce')
1052 ), admin_url('admin.php?page=mxchat-prompts')),
1053 'format' => '',
1054 'prev_text' => __('&laquo; Previous', 'mxchat'),
1055 'next_text' => __('Next &raquo;', 'mxchat'),
1056 'total' => $total_pages,
1057 'current' => $current_page,
1058 ));
1059
1060 // Retrieve processing statuses
1061 $pdf_url = get_transient('mxchat_last_pdf_url');
1062 $sitemap_url = get_transient('mxchat_last_sitemap_url');
1063 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
1064 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
1065
1066 if ($pdf_status && $pdf_status['status'] === 'complete') {
1067 delete_transient('mxchat_last_pdf_url');
1068 $pdf_status = false;
1069 }
1070 if ($sitemap_status && $sitemap_status['status'] === 'complete') {
1071 delete_transient('mxchat_last_sitemap_url');
1072 $sitemap_status = false;
1073 }
1074
1075 $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error'))
1076 || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error'));
1077 ?>
1078 <div class="wrap mxchat-wrapper">
1079 <!-- Hero Section -->
1080 <div class="mxchat-hero">
1081 <h1 class="mxchat-main-title">
1082 <span class="mxchat-gradient-text">Knowledge Base</span> Manager
1083 </h1>
1084 <p class="mxchat-hero-subtitle">
1085 <?php esc_html_e('Enhance your AI chatbot with custom knowledge. Import, manage, and organize your content to keep responses accurate and relevant.', 'mxchat'); ?>
1086 </p>
1087 </div>
1088
1089 <div class="mxchat-content">
1090 <!-- Import Settings Card -->
1091 <div class="mxchat-card">
1092 <h2><?php esc_html_e('Knowledge Import Settings', 'mxchat'); ?></h2>
1093 <?php
1094 // Check if the appropriate embedding API key exists
1095 $embedding_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
1096 $has_openai_key = !empty($this->options['api_key']);
1097 $has_voyage_key = !empty($this->options['voyage_api_key']);
1098
1099 // Determine if they have the needed API key for their selected embedding model
1100 $has_required_key = false;
1101 $required_key_type = '';
1102
1103 if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) {
1104 $has_required_key = true;
1105 $required_key_type = 'OpenAI';
1106 } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) {
1107 $has_required_key = true;
1108 $required_key_type = 'Voyage AI';
1109 } elseif (strpos($embedding_model, 'text-embedding-') !== false) {
1110 $required_key_type = 'OpenAI';
1111 } elseif (strpos($embedding_model, 'voyage-') !== false) {
1112 $required_key_type = 'Voyage AI';
1113 }
1114 ?>
1115
1116 <div class="mxchat-knowledge-warning <?php echo $has_required_key ? 'success' : 'warning'; ?>">
1117 <?php if ($has_required_key): ?>
1118 <p><span class="dashicons dashicons-yes-alt"></span> <?php echo wp_kses_post(sprintf(__('We detected your %s API key. <strong>You must have added credits to your %s account</strong> before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?></p>
1119 <?php else: ?>
1120 <p><span class="dashicons dashicons-warning"></span> <strong><?php esc_html_e('Important:', 'mxchat'); ?></strong> <?php echo sprintf(esc_html__('Before importing knowledge, you must add a %s API key with sufficient credits in the Chatbot settings.', 'mxchat'), $required_key_type); ?> <a href="<?php echo admin_url('admin.php?page=mxchat-max'); ?>"><?php esc_html_e('Go to API Key Settings', 'mxchat'); ?></a></p>
1121 <?php endif; ?>
1122 </div>
1123
1124 <!-- Tab Contents -->
1125 <div class="mxchat-tab-contents">
1126 <!-- Default Database Tab -->
1127 <div id="default-db" class="mxchat-tab-content active">
1128 <!-- Auto-Sync Settings -->
1129 <div class="mxchat-settings-section">
1130 <h3><?php esc_html_e('Auto-Sync Settings', 'mxchat'); ?></h3>
1131 <p class="mxchat-description">
1132 <?php esc_html_e('Note: Auto-sync works only for newly published content. Existing posts and pages must be imported manually below. Works with Pinecone if enabled', 'mxchat'); ?>
1133 </p>
1134 <div class="mxchat-autosave-section"> <!-- Add this wrapper -->
1135 <div class="mxchat-toggle-group">
1136 <div class="mxchat-toggle-container">
1137 <label class="mxchat-toggle-switch">
1138 <input type="checkbox"
1139 name="mxchat_auto_sync_posts"
1140 class="mxchat-autosave-field"
1141 value="1"
1142 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1143 <?php checked(get_option('mxchat_auto_sync_posts', '0'), '1'); ?>>
1144 <span class="mxchat-toggle-slider"></span>
1145 </label>
1146 <span class="mxchat-toggle-label">
1147 <?php esc_html_e('Auto-sync Posts', 'mxchat'); ?>
1148 </span>
1149 </div>
1150
1151 <div class="mxchat-toggle-container">
1152 <label class="mxchat-toggle-switch">
1153 <input type="checkbox"
1154 name="mxchat_auto_sync_pages"
1155 class="mxchat-autosave-field"
1156 value="1"
1157 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1158 <?php checked(get_option('mxchat_auto_sync_pages', '0'), '1'); ?>>
1159 <span class="mxchat-toggle-slider"></span>
1160 </label>
1161 <span class="mxchat-toggle-label">
1162 <?php esc_html_e('Auto-sync Pages', 'mxchat'); ?>
1163 </span>
1164 </div>
1165
1166
1167
1168
1169 <!-- Replace the existing custom post types section with this code -->
1170 <div class="mxchat-section-content">
1171 <div class="mxchat-custom-post-types-header">
1172 <button id="mxchat-custom-post-types-toggle" class="mxchat-button-secondary">
1173 <?php esc_html_e('Advanced Custom Post Sync Settings', 'mxchat'); ?>
1174 <span class="mxchat-toggle-icon"></span>
1175 </button>
1176 </div>
1177
1178 <div id="mxchat-custom-post-types-container" class="mxchat-custom-post-types-container" style="display: none;">
1179 <h3><?php esc_html_e('Sync Custom Post Types', 'mxchat'); ?></h3>
1180 <p><?php esc_html_e('Select additional custom post types to automatically sync with the chatbot.', 'mxchat'); ?></p>
1181
1182 <div class="mxchat-custom-post-types">
1183 <?php
1184 $post_types = $this->get_public_post_types();
1185
1186 // Skip post and page as they're handled separately
1187 unset($post_types['post']);
1188 unset($post_types['page']);
1189
1190 if (!empty($post_types)) {
1191 foreach ($post_types as $post_type => $label) {
1192 $option_name = 'mxchat_auto_sync_' . $post_type;
1193 $is_enabled = get_option($option_name, '0');
1194 ?>
1195 <div class="mxchat-toggle-container">
1196 <label class="mxchat-toggle-switch">
1197 <input type="checkbox"
1198 name="<?php echo esc_attr($option_name); ?>"
1199 class="mxchat-autosave-field"
1200 value="1"
1201 data-nonce="<?php echo wp_create_nonce('mxchat_prompts_setting_nonce'); ?>"
1202 <?php checked($is_enabled, '1'); ?>>
1203 <span class="mxchat-toggle-slider"></span>
1204 </label>
1205 <span class="mxchat-toggle-label">
1206 <?php echo esc_html($label); ?> (<?php echo esc_html($post_type); ?>)
1207 </span>
1208 </div>
1209 <?php
1210 }
1211 } else {
1212 echo '<p>' . esc_html__('No custom post types found.', 'mxchat') . '</p>';
1213 }
1214 ?>
1215 </div>
1216 </div>
1217 </div>
1218
1219
1220
1221 </div>
1222 </div>
1223 </div>
1224
1225 <!-- Import Methods -->
1226 <div class="mxchat-import-methods">
1227 <div class="mxchat-method-card">
1228 <h4><?php esc_html_e('Sitemap Import', 'mxchat'); ?></h4>
1229 <p><span class="red-warning">IMPORTANT:</span> <?php esc_html_e('Use a content-specific sub-sitemap, not the sitemap index.', 'mxchat'); ?></p> </div>
1230 <div class="mxchat-method-card">
1231 <h4><?php esc_html_e('PDF Import', 'mxchat'); ?></h4>
1232 <p><?php esc_html_e('Import knowledge directly from PDF documents.', 'mxchat'); ?></p>
1233 </div>
1234 <div class="mxchat-method-card">
1235 <h4><?php esc_html_e('Direct URL', 'mxchat'); ?></h4>
1236 <p><?php esc_html_e('Import content from any specific webpage.', 'mxchat'); ?></p>
1237 </div>
1238 <div class="mxchat-method-card">
1239 <h4><?php esc_html_e('Direct Content', 'mxchat'); ?></h4>
1240 <p><?php esc_html_e('Directly submit content to be vectorized.', 'mxchat'); ?></p>
1241 </div>
1242 </div>
1243
1244 <!-- Import Form -->
1245 <?php if (!$is_processing) : ?>
1246 <div class="mxchat-import-form">
1247 <form id="mxchat-sitemap-form" method="post"
1248 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_sitemap')); ?>">
1249 <?php wp_nonce_field('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); ?>
1250 <div class="mxchat-url-input-group">
1251 <input type="url"
1252 name="sitemap_url"
1253 id="sitemap_url"
1254 placeholder="<?php esc_attr_e('Enter Sitemap, PDF, or Webpage URL', 'mxchat'); ?>"
1255 required />
1256 <button type="submit"
1257 name="submit_sitemap"
1258 class="mxchat-button-primary">
1259 <?php esc_html_e('Import', 'mxchat'); ?>
1260 </button>
1261 </div>
1262 </form>
1263 </div>
1264 <?php endif; ?>
1265
1266 <!-- Processing Status -->
1267 <?php if ($pdf_status && $pdf_status['status'] !== 'complete') : ?>
1268 <div class="mxchat-status-card">
1269 <div class="mxchat-status-header">
1270 <h4><?php esc_html_e('PDF Processing Status', 'mxchat'); ?></h4>
1271 <?php if ($is_processing) : ?>
1272 <form method="post" class="mxchat-stop-form"
1273 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1274 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1275 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1276 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1277 </button>
1278 </form>
1279 <?php endif; ?>
1280
1281 <?php if ($pdf_status['status'] === 'error') : ?>
1282 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Error', 'mxchat'); ?></span>
1283 <?php endif; ?>
1284 </div>
1285 <div class="mxchat-progress-bar">
1286 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($pdf_status['percentage']); ?>%"></div>
1287 </div>
1288 <div class="mxchat-status-details">
1289 <p><?php printf(
1290 esc_html__('Progress: %1$d of %2$d pages (%3$d%%)', 'mxchat'),
1291 absint($pdf_status['processed_pages']),
1292 absint($pdf_status['total_pages']),
1293 absint($pdf_status['percentage'])
1294 ); ?></p>
1295 <p><?php printf(
1296 esc_html__('Status: %s', 'mxchat'),
1297 esc_html(ucfirst($pdf_status['status']))
1298 ); ?></p>
1299 <p><?php printf(
1300 esc_html__('Last update: %s', 'mxchat'),
1301 esc_html($pdf_status['last_update'])
1302 ); ?></p>
1303
1304 <?php if (!empty($pdf_status['error'])) : ?>
1305 <div class="mxchat-error-notice">
1306 <p class="error"><?php echo esc_html($pdf_status['error']); ?></p>
1307 </div>
1308 <?php endif; ?>
1309 </div>
1310 </div>
1311 <?php endif; ?>
1312
1313
1314 <!-- Single URL Submission Status -->
1315 <?php
1316 // Get single URL status
1317 $single_url_status = $this->get_single_url_status();
1318 $is_active_processing =
1319 ($sitemap_status && $sitemap_status['status'] === 'processing') ||
1320 ($pdf_status && $pdf_status['status'] === 'processing');
1321 ?>
1322
1323 <div id="mxchat-single-url-status-container" <?php echo $is_active_processing ? 'style="display:none;"' : ''; ?>>
1324 <?php if ($single_url_status && !$is_active_processing) : ?>
1325 <div class="mxchat-status-card">
1326 <div class="mxchat-status-header">
1327 <h4><?php esc_html_e('Last URL Submission', 'mxchat'); ?></h4>
1328 <?php if ($single_url_status['status'] === 'failed') : ?>
1329 <span class="mxchat-status-badge mxchat-status-failed"><?php esc_html_e('Failed', 'mxchat'); ?></span>
1330 <?php else : ?>
1331 <span class="mxchat-status-badge mxchat-status-success"><?php esc_html_e('Success', 'mxchat'); ?></span>
1332 <?php endif; ?>
1333 </div>
1334 <div class="mxchat-status-details">
1335 <p><strong><?php esc_html_e('URL:', 'mxchat'); ?></strong>
1336 <a href="<?php echo esc_url($single_url_status['url']); ?>" target="_blank">
1337 <?php echo esc_html(strlen($single_url_status['url']) > 60 ? substr($single_url_status['url'], 0, 57) . '...' : $single_url_status['url']); ?>
1338 </a>
1339 </p>
1340 <p><strong><?php esc_html_e('Submitted:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['human_time']); ?></p>
1341
1342 <?php if ($single_url_status['status'] === 'failed' && !empty($single_url_status['error'])) : ?>
1343 <div class="mxchat-error-notice">
1344 <p class="error"><?php echo esc_html($single_url_status['error']); ?></p>
1345 </div>
1346 <?php endif; ?>
1347
1348 <?php if ($single_url_status['status'] === 'complete') : ?>
1349 <p><strong><?php esc_html_e('Content Length:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['content_length']); ?> <?php esc_html_e('characters', 'mxchat'); ?></p>
1350 <p><strong><?php esc_html_e('Embedding Dimensions:', 'mxchat'); ?></strong> <?php echo esc_html($single_url_status['embedding_dimensions']); ?></p>
1351 <?php endif; ?>
1352 </div>
1353 </div>
1354 <?php endif; ?>
1355 </div>
1356
1357
1358 <?php if ($sitemap_status && $sitemap_status['status'] !== 'complete') : ?>
1359 <div class="mxchat-status-card">
1360 <div class="mxchat-status-header">
1361 <h4><?php esc_html_e('Sitemap Processing Status', 'mxchat'); ?></h4>
1362 <?php if ($is_processing) : ?>
1363 <form method="post" class="mxchat-stop-form"
1364 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_stop_processing')); ?>">
1365 <?php wp_nonce_field('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); ?>
1366 <button type="submit" name="stop_processing" class="mxchat-button-secondary">
1367 <?php esc_html_e('Stop Processing', 'mxchat'); ?>
1368 </button>
1369 </form>
1370 <?php endif; ?>
1371 </div>
1372 <div class="mxchat-progress-bar">
1373 <div class="mxchat-progress-fill" style="width: <?php echo esc_attr($sitemap_status['percentage']); ?>%"></div>
1374 </div>
1375 <div class="mxchat-status-details">
1376 <p><?php printf(
1377 esc_html__('Progress: %1$d of %2$d URLs (%3$d%%)', 'mxchat'),
1378 absint($sitemap_status['processed_urls']),
1379 absint($sitemap_status['total_urls']),
1380 absint($sitemap_status['percentage'])
1381 ); ?></p>
1382 <?php if (!empty($sitemap_status['error']) || !empty($sitemap_status['last_error'])) : ?>
1383 <div class="mxchat-error-notice">
1384 <?php if (!empty($sitemap_status['error'])) : ?>
1385 <p class="error"><?php echo esc_html($sitemap_status['error']); ?></p>
1386 <?php endif; ?>
1387 <?php if (!empty($sitemap_status['last_error'])) : ?>
1388 <p class="last-error"><?php echo esc_html__('Last error:', 'mxchat') . ' ' . esc_html($sitemap_status['last_error']); ?></p>
1389 <?php endif; ?>
1390 </div>
1391 <?php endif; ?>
1392
1393 <?php if (!empty($sitemap_status['failed_urls']) && $sitemap_status['failed_urls'] > 0) : ?>
1394 <div class="mxchat-failed-urls">
1395 <h5><?php echo sprintf(esc_html__('Failed URLs (%d)', 'mxchat'), absint($sitemap_status['failed_urls'])); ?></h5>
1396 <?php if (!empty($sitemap_status['failed_urls_list'])) : ?>
1397 <div class="mxchat-failed-urls-list" style="max-height: 200px; overflow-y: auto; margin-top: 10px;">
1398 <table class="widefat striped">
1399 <thead>
1400 <tr>
1401 <th><?php esc_html_e('URL', 'mxchat'); ?></th>
1402 <th><?php esc_html_e('Error', 'mxchat'); ?></th>
1403 <th><?php esc_html_e('Time', 'mxchat'); ?></th>
1404 </tr>
1405 </thead>
1406 <tbody>
1407 <?php foreach ($sitemap_status['failed_urls_list'] as $failed_url) : ?>
1408 <tr>
1409 <td style="word-break: break-all;">
1410 <a href="<?php echo esc_url($failed_url['url']); ?>" target="_blank" rel="noopener noreferrer">
1411 <?php echo esc_html(strlen($failed_url['url']) > 60 ? substr($failed_url['url'], 0, 57) . '...' : $failed_url['url']); ?>
1412 </a>
1413 </td>
1414 <td><?php echo esc_html($failed_url['error']); ?></td>
1415 <td><?php echo esc_html(human_time_diff($failed_url['time'], time()) . ' ' . __('ago', 'mxchat')); ?></td>
1416 </tr>
1417 <?php endforeach; ?>
1418 </tbody>
1419 </table>
1420 </div>
1421 <?php endif; ?>
1422 </div>
1423 <?php endif; ?>
1424 </div>
1425 </div>
1426 <?php endif; ?>
1427
1428
1429 </div>
1430
1431
1432 </div>
1433
1434
1435
1436 </div>
1437
1438 <!-- Direct Content Submission Card -->
1439 <div class="mxchat-card">
1440 <div class="mxchat-card-header">
1441 <h2><?php esc_html_e('Submit Direct Content', 'mxchat'); ?></h2>
1442 </div>
1443 <form id="mxchat-content-form" method="post"
1444 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_submit_content')); ?>">
1445 <?php wp_nonce_field('mxchat_submit_content_action', 'mxchat_submit_content_nonce'); ?>
1446 <div class="mxchat-form-group">
1447 <label for="article_content"><?php esc_html_e('Content:', 'mxchat'); ?></label>
1448 <textarea name="article_content" id="article_content" required
1449 placeholder="<?php esc_attr_e('Enter your content here...', 'mxchat'); ?>"></textarea>
1450 </div>
1451 <div class="mxchat-form-group">
1452 <label for="article_url"><?php esc_html_e('Source URL (Optional):', 'mxchat'); ?></label>
1453 <input type="url" name="article_url" id="article_url"
1454 placeholder="<?php esc_attr_e('Enter source URL', 'mxchat'); ?>">
1455 </div>
1456 <button type="submit" name="submit_content" class="mxchat-button-primary">
1457 <?php esc_html_e('Submit Content', 'mxchat'); ?>
1458 </button>
1459 </form>
1460 </div>
1461
1462 <!-- Knowledge Base Table Card -->
1463 <div class="mxchat-card">
1464 <div class="mxchat-card-header">
1465 <h2><?php esc_html_e('Knowledge Base', 'mxchat'); ?></h2>
1466 <div class="mxchat-header-actions">
1467 <!-- Search -->
1468 <form method="get" id="knowledge-search" class="mxchat-search-form">
1469 <?php wp_nonce_field('mxchat_prompts_search_nonce'); ?>
1470 <input type="hidden" name="page" value="mxchat-prompts" />
1471 <div class="mxchat-search-group">
1472 <span class="dashicons dashicons-search"></span>
1473 <input type="text"
1474 name="search"
1475 placeholder="<?php esc_attr_e('Search Knowledge', 'mxchat'); ?>"
1476 value="<?php echo esc_attr($search_query); ?>" />
1477 </div>
1478 </form>
1479
1480 <!-- Delete All -->
1481 <form method="post"
1482 action="<?php echo esc_url(admin_url('admin-post.php?action=mxchat_delete_all_prompts')); ?>"
1483 class="mxchat-delete-form"
1484 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete all knowledge? This action cannot be undone.', 'mxchat'); ?>');">
1485 <?php wp_nonce_field('mxchat_delete_all_prompts_action', 'mxchat_delete_all_prompts_nonce'); ?>
1486 <button type="submit" name="delete_all_prompts" class="mxchat-button-danger">
1487 <span class="dashicons dashicons-trash"></span>
1488 <?php esc_html_e('Delete All', 'mxchat'); ?>
1489 </button>
1490 </form>
1491 </div>
1492 </div>
1493
1494 <!-- Table -->
1495 <div class="mxchat-table-wrapper">
1496 <table class="mxchat-records-table">
1497 <thead>
1498 <tr>
1499 <th><?php esc_html_e('ID', 'mxchat'); ?></th>
1500 <th><?php esc_html_e('Content', 'mxchat'); ?></th>
1501 <th><?php esc_html_e('Source', 'mxchat'); ?></th>
1502 <th><?php esc_html_e('Actions', 'mxchat'); ?></th>
1503 </tr>
1504 </thead>
1505 <tbody>
1506 <?php if ($prompts) : ?>
1507 <?php foreach ($prompts as $prompt) : ?>
1508 <tr id="prompt-<?php echo esc_attr($prompt->id); ?>">
1509 <td><?php echo esc_html($prompt->id); ?></td>
1510 <td class="mxchat-content-cell">
1511 <div class="content-view">
1512 <?php echo wp_kses_post(wpautop(esc_textarea($prompt->article_content))); ?>
1513 </div>
1514 <textarea class="content-edit" style="display:none;">
1515 <?php echo esc_textarea($prompt->article_content); ?>
1516 </textarea>
1517 </td>
1518 <td class="mxchat-url-cell">
1519 <div class="url-view">
1520 <?php if (!empty($prompt->source_url)) : ?>
1521 <a href="<?php echo esc_url($prompt->source_url); ?>" target="_blank">
1522 <span class="dashicons dashicons-external"></span>
1523 <?php esc_html_e('View Source', 'mxchat'); ?>
1524 </a>
1525 <?php else : ?>
1526 <span class="mxchat-na"><?php esc_html_e('N/A', 'mxchat'); ?></span>
1527 <?php endif; ?>
1528 </div>
1529 <input type="text" class="url-edit" style="display:none;" value="<?php echo esc_attr($prompt->source_url); ?>" />
1530 </td>
1531 <td class="mxchat-actions-cell">
1532 <button class="mxchat-button-icon edit-button"
1533 data-id="<?php echo esc_attr($prompt->id); ?>">
1534 <span class="dashicons dashicons-edit"></span>
1535 </button>
1536 <button class="mxchat-button-icon save-button"
1537 data-id="<?php echo esc_attr($prompt->id); ?>"
1538 style="display:none;"
1539 data-nonce="<?php echo wp_create_nonce('mxchat_save_inline_nonce'); ?>">
1540 <span class="dashicons dashicons-saved"></span>
1541 </button>
1542 <a href="<?php echo esc_url(admin_url(
1543 'admin-post.php?action=mxchat_delete_prompt&id=' . esc_attr($prompt->id)
1544 . '&_wpnonce=' . wp_create_nonce('mxchat_delete_prompt_nonce')
1545 )); ?>"
1546 class="mxchat-button-icon delete-button"
1547 onclick="return confirm('<?php esc_attr_e('Are you sure you want to delete this entry?', 'mxchat'); ?>');">
1548 <span class="dashicons dashicons-trash"></span>
1549 </a>
1550 </td>
1551 </tr>
1552 <?php endforeach; ?>
1553 <?php else : ?>
1554 <tr>
1555 <td colspan="4" class="mxchat-no-records">
1556 <?php esc_html_e('No knowledge base entries found.', 'mxchat'); ?>
1557 </td>
1558 </tr>
1559 <?php endif; ?>
1560 </tbody>
1561 </table>
1562 </div>
1563
1564 <?php if ($page_links) : ?>
1565 <div class="mxchat-pagination">
1566 <?php echo wp_kses_post($page_links); ?>
1567 </div>
1568 <?php endif; ?>
1569 </div>
1570 </div>
1571 </div>
1572 <?php
1573 }
1574
1575
1576 public function mxchat_handle_delete_all_prompts() {
1577 // Verify nonce
1578 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
1579 wp_die(__('Nonce verification failed.', 'mxchat'));
1580 }
1581
1582 // Check permissions
1583 if (!current_user_can('manage_options')) {
1584 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
1585 }
1586
1587 global $wpdb;
1588 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1589
1590 // Delete all prompts from the table
1591 $wpdb->query("DELETE FROM {$table_name}");
1592
1593 // Clear relevant cache
1594 wp_cache_delete('all_prompts', 'mxchat_prompts');
1595
1596 // Redirect back with a success message
1597 $redirect_url = add_query_arg(array(
1598 'page' => 'mxchat-prompts',
1599 'all_deleted' => 'true'
1600 ), admin_url('admin.php'));
1601
1602 wp_safe_redirect($redirect_url);
1603 exit;
1604 }
1605 public function mxchat_handle_delete_prompt() {
1606 // Sanitize and validate nonce
1607 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
1608 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
1609 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
1610 }
1611
1612 // Check permissions
1613 if (!current_user_can('manage_options')) {
1614 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
1615 }
1616
1617 // Validate and sanitize ID parameter
1618 $id = isset($_GET['id']) ? intval($_GET['id']) : 0;
1619 if ($id <= 0) {
1620 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
1621 }
1622
1623 global $wpdb;
1624 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1625
1626 // Clear cache and delete prompt
1627 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
1628 $wpdb->delete($table_name, array('id' => $id), array('%d'));
1629
1630 wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php')));
1631 exit;
1632 }
1633
1634
1635 public function mxchat_generate_embedding($text) {
1636 // Enable detailed logging for debugging
1637 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
1638 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
1639
1640 $options = get_option('mxchat_options');
1641 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
1642 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
1643
1644 // Determine provider and endpoint
1645 if (strpos($selected_model, 'voyage') === 0) {
1646 $api_key = $options['voyage_api_key'] ?? '';
1647 $endpoint = 'https://api.voyageai.com/v1/embeddings';
1648 $provider_name = 'Voyage AI';
1649 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
1650 } else {
1651 $api_key = $options['api_key'] ?? '';
1652 $endpoint = 'https://api.openai.com/v1/embeddings';
1653 $provider_name = 'OpenAI';
1654 //error_log('[MXCHAT-EMBED] Using OpenAI API');
1655 }
1656
1657 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
1658
1659 if (empty($api_key)) {
1660 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
1661 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
1662 return $error_message;
1663 }
1664
1665 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
1666 $estimated_tokens = ceil(str_word_count($text) / 0.75);
1667 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
1668
1669 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false) {
1670 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
1671 // Consider truncating text here
1672 }
1673
1674 // Prepare request body
1675 $request_body = array(
1676 'model' => $selected_model,
1677 'input' => $text
1678 );
1679
1680 // Add output_dimension for voyage-3-large model
1681 if ($selected_model === 'voyage-3-large') {
1682 $request_body['output_dimension'] = 2048;
1683 }
1684
1685 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
1686
1687 // Make API request
1688 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
1689 $response = wp_remote_post($endpoint, array(
1690 'body' => wp_json_encode($request_body),
1691 'headers' => array(
1692 'Authorization' => 'Bearer ' . $api_key,
1693 'Content-Type' => 'application/json'
1694 ),
1695 'timeout' => 60 // Increased timeout for large inputs
1696 ));
1697
1698 // Handle wp_remote_post errors
1699 if (is_wp_error($response)) {
1700 $error_message = $response->get_error_message();
1701 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
1702 return 'Connection error: ' . $error_message;
1703 }
1704
1705 // Get and check HTTP response code
1706 $http_code = wp_remote_retrieve_response_code($response);
1707 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
1708
1709 if ($http_code !== 200) {
1710 $error_body = wp_remote_retrieve_body($response);
1711 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
1712
1713 // Try to parse error for more details
1714 $error_json = json_decode($error_body, true);
1715 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
1716 $error_type = $error_json['error']['type'] ?? 'unknown';
1717 $error_message = $error_json['error']['message'] ?? 'No message';
1718 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
1719 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
1720
1721 // Customize error message for common API errors
1722 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
1723 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
1724 } elseif ($error_type === 'authentication_error') {
1725 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
1726 }
1727
1728 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
1729 return $error_message;
1730 }
1731
1732 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
1733 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
1734 return $error_message;
1735 }
1736
1737 // Parse response body
1738 $response_body = wp_remote_retrieve_body($response);
1739 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
1740
1741 $response_data = json_decode($response_body, true);
1742
1743 if (json_last_error() !== JSON_ERROR_NONE) {
1744 $error = json_last_error_msg();
1745 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
1746 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
1747 return "Failed to parse API response: $error";
1748 }
1749
1750 // Both APIs use the same structure, so we can extract the embedding the same way
1751 if (isset($response_data['data'][0]['embedding'])) {
1752 $embedding_dimensions = count($response_data['data'][0]['embedding']);
1753 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
1754
1755 // Check if embedding dimensions are as expected
1756 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
1757 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
1758 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
1759 }
1760
1761 return $response_data['data'][0]['embedding'];
1762 } else {
1763 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
1764 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
1765
1766 if (isset($response_data['error'])) {
1767 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
1768 //error_log('[MXCHAT-EMBED] ' . $error_message);
1769 return $error_message;
1770 }
1771
1772 $error_message = "Invalid API response format: No embedding found";
1773 //error_log('[MXCHAT-EMBED] ' . $error_message);
1774 return $error_message;
1775 }
1776 }
1777
1778
1779 public function mxchat_delete_chat_history() {
1780 if (!current_user_can('manage_options')) {
1781 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
1782 wp_die();
1783 }
1784
1785 check_ajax_referer('mxchat_delete_chat_history', 'security');
1786
1787 global $wpdb;
1788 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1789
1790 if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
1791 foreach ($_POST['delete_session_ids'] as $session_id) {
1792 $session_id_sanitized = sanitize_text_field($session_id);
1793
1794 // Clear relevant cache before deletion
1795 $cache_key = 'chat_session_' . $session_id_sanitized;
1796 wp_cache_delete($cache_key, 'mxchat_chat_sessions');
1797
1798 // Perform the deletion
1799 $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
1800 }
1801
1802 // Optionally, clear a general cache if you have one
1803 wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
1804
1805 echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]);
1806 } else {
1807 echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
1808 }
1809
1810 wp_die();
1811 }
1812
1813
1814 public function mxchat_save_inline_prompt() {
1815 // Check for nonce security
1816 check_ajax_referer('mxchat_save_inline_nonce');
1817
1818 // Verify permissions
1819 if (!current_user_can('manage_options')) {
1820 wp_send_json_error(esc_html__('Permission denied.', 'mxchat'));
1821 return;
1822 }
1823
1824 global $wpdb;
1825 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
1826
1827 // Validate and sanitize input data
1828 $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
1829 $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : '';
1830 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
1831
1832 if ($prompt_id > 0 && !empty($article_content)) {
1833 // Re-generate the embedding vector for the updated content
1834 $embedding_vector = $this->mxchat_generate_embedding($article_content);
1835
1836 if (is_array($embedding_vector)) {
1837 // Serialize the embedding vector before storing it
1838 $embedding_vector_serialized = serialize($embedding_vector);
1839
1840 // Update the prompt in the database
1841 $updated = $wpdb->update(
1842 $table_name,
1843 array(
1844 'article_content' => $article_content,
1845 'embedding_vector' => $embedding_vector_serialized,
1846 'source_url' => $article_url,
1847 ),
1848 array('id' => $prompt_id),
1849 array('%s', '%s', '%s'),
1850 array('%d')
1851 );
1852
1853 if ($updated !== false) {
1854 wp_send_json_success();
1855 } else {
1856 wp_send_json_error(esc_html__('Database update failed.', 'mxchat'));
1857 }
1858 } else {
1859 wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat'));
1860 }
1861 } else {
1862 wp_send_json_error(esc_html__('Invalid data.', 'mxchat'));
1863 }
1864 }
1865
1866
1867
1868 /**
1869 * Get all public post types
1870 *
1871 * @return array Associative array of post type names and labels
1872 */
1873 private function get_public_post_types() {
1874 $post_types = get_post_types(array('public' => true), 'objects');
1875 $post_type_options = array();
1876
1877 foreach ($post_types as $post_type) {
1878 $post_type_options[$post_type->name] = $post_type->label;
1879 }
1880
1881 return $post_type_options;
1882 }
1883
1884 /**
1885 * Handle post updates and process content for the chatbot
1886 *
1887 * @param int $post_id The ID of the post being saved
1888 * @param WP_Post $post The post object
1889 * @param bool $update Whether this is an existing post being updated
1890 */
1891 public function handle_post_update($post_id, $post, $update) {
1892 // Basic validation checks
1893 if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) {
1894 return;
1895 }
1896
1897 // Only process published content
1898 if ($post->post_status !== 'publish') {
1899 return;
1900 }
1901
1902 $post_type = $post->post_type;
1903
1904 // Check if sync is enabled for this post type
1905 $should_sync = false;
1906
1907 // Check built-in post types first
1908 if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
1909 $should_sync = true;
1910 } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
1911 $should_sync = true;
1912 } else {
1913 // Check custom post types
1914 $option_name = 'mxchat_auto_sync_' . $post_type;
1915 if (get_option($option_name) === '1') {
1916 $should_sync = true;
1917 }
1918 }
1919
1920 if (!$should_sync) {
1921 return;
1922 }
1923
1924 // Prepare content and URL
1925 $content = wp_strip_all_tags($post->post_content);
1926
1927 // For custom post types like job_listing, include additional fields
1928 if ($post_type === 'job_listing') {
1929 // Add title as it's important for job listings
1930 $content = $post->post_title . "\n\n" . $content;
1931
1932 // Add job-specific meta if available
1933 $job_location = get_post_meta($post_id, '_job_location', true);
1934 if (!empty($job_location)) {
1935 $content .= "\n\nLocation: " . $job_location;
1936 }
1937
1938 // Get job type terms
1939 $job_types = get_the_terms($post_id, 'job_listing_type');
1940 if (!empty($job_types) && !is_wp_error($job_types)) {
1941 $types = array();
1942 foreach ($job_types as $type) {
1943 $types[] = $type->name;
1944 }
1945 $content .= "\n\nJob Type: " . implode(', ', $types);
1946 }
1947
1948 // Get company name if available
1949 $company_name = get_post_meta($post_id, '_company_name', true);
1950 if (!empty($company_name)) {
1951 $content .= "\n\nCompany: " . $company_name;
1952 }
1953 }
1954
1955 $url = get_permalink($post_id);
1956
1957 // Generate embedding vector
1958 $embedding_vector = $this->mxchat_generate_embedding($content);
1959 if (!$embedding_vector) {
1960 return;
1961 }
1962
1963 // Check for Pinecone addon and its settings
1964 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
1965 $use_pinecone = false;
1966 if ($pinecone_settings && is_array($pinecone_settings)) {
1967 // Check if Pinecone is enabled and all required settings are present
1968 $use_pinecone = (
1969 isset($pinecone_settings['mxchat_use_pinecone']) &&
1970 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
1971 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
1972 !empty($pinecone_settings['mxchat_pinecone_host']) &&
1973 !empty($pinecone_settings['mxchat_pinecone_index']) &&
1974 !empty($pinecone_settings['mxchat_pinecone_environment'])
1975 );
1976 }
1977
1978 if ($use_pinecone) {
1979 // Use Pinecone
1980 $pinecone_result = $this->store_in_pinecone_main(
1981 $embedding_vector,
1982 $content,
1983 $url,
1984 $pinecone_settings['mxchat_pinecone_api_key'],
1985 $pinecone_settings['mxchat_pinecone_environment'],
1986 $pinecone_settings['mxchat_pinecone_index']
1987 );
1988 if (!$pinecone_result['success']) {
1989 // Fallback to WordPress DB
1990 $this->store_in_wordpress_db($content, $url, $embedding_vector);
1991 }
1992 } else {
1993 // Fallback to WordPress DB storage
1994 $this->store_in_wordpress_db($content, $url, $embedding_vector);
1995 }
1996
1997 }
1998 /**
1999 * Handle deletion of posts from both Pinecone and WordPress DB
2000 *
2001 * @param int $post_id The ID of the post being deleted
2002 * @return void
2003 */
2004 public function mxchat_handle_post_delete($post_id) {
2005 // Get post data before it's deleted
2006 $post = get_post($post_id);
2007
2008 // Basic validation
2009 if (!$post || wp_is_post_revision($post_id)) {
2010 return;
2011 }
2012
2013 $post_type = $post->post_type;
2014
2015 // Check if sync is enabled for this post type
2016 $should_sync = false;
2017
2018 // Check built-in post types first
2019 if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') {
2020 $should_sync = true;
2021 } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') {
2022 $should_sync = true;
2023 } else {
2024 // Check custom post types
2025 $option_name = 'mxchat_auto_sync_' . $post_type;
2026 if (get_option($option_name) === '1') {
2027 $should_sync = true;
2028 }
2029 }
2030
2031 if (!$should_sync) {
2032 return;
2033 }
2034
2035 // Get the URL before post is deleted
2036 $source_url = get_permalink($post_id);
2037 if (!$source_url) {
2038 //error_log('MXChat: Failed to get permalink for post ' . $post_id);
2039 return;
2040 }
2041
2042 // Check for Pinecone addon and its settings
2043 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
2044 $use_pinecone = false;
2045 if ($pinecone_settings && is_array($pinecone_settings)) {
2046 $use_pinecone = (
2047 isset($pinecone_settings['mxchat_use_pinecone']) &&
2048 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
2049 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
2050 !empty($pinecone_settings['mxchat_pinecone_host']) &&
2051 !empty($pinecone_settings['mxchat_pinecone_index']) &&
2052 !empty($pinecone_settings['mxchat_pinecone_environment'])
2053 );
2054 }
2055
2056 $deletion_successful = false;
2057
2058 if ($use_pinecone) {
2059 try {
2060 $pinecone_result = $this->delete_from_pinecone(
2061 array($source_url),
2062 $pinecone_settings['mxchat_pinecone_api_key'],
2063 $pinecone_settings['mxchat_pinecone_environment'],
2064 $pinecone_settings['mxchat_pinecone_index']
2065 );
2066
2067 if (!$pinecone_result['success']) {
2068 //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']);
2069 } else {
2070 $deletion_successful = true;
2071 }
2072 } catch (Exception $e) {
2073 //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage());
2074 }
2075 }
2076
2077 // Always attempt WordPress DB deletion, regardless of Pinecone status
2078 try {
2079 global $wpdb;
2080 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2081
2082 $result = $wpdb->delete(
2083 $table_name,
2084 array('source_url' => $source_url),
2085 array('%s')
2086 );
2087
2088 if ($result === false) {
2089 //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url);
2090 } else {
2091 $deletion_successful = true;
2092 }
2093 } catch (Exception $e) {
2094 //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage());
2095 }
2096
2097 if (!$deletion_successful) {
2098 //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url);
2099 } else {
2100
2101 }
2102 }
2103
2104
2105
2106
2107
2108
2109
2110 // Modified storage function to add type field
2111 private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) {
2112 $vector_id = $vector_id ?: md5($url);
2113 $options = get_option('mxchat_pinecone_addon_options');
2114 $host = $options['mxchat_pinecone_host'] ?? '';
2115
2116 if (empty($host)) {
2117 return array(
2118 'success' => false,
2119 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
2120 );
2121 }
2122
2123 // Determine if this is a product URL
2124 $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false);
2125
2126 $api_endpoint = "https://{$host}/vectors/upsert";
2127 $request_body = array(
2128 'vectors' => array(
2129 array(
2130 'id' => $vector_id,
2131 'values' => $embedding_vector,
2132 'metadata' => array(
2133 'text' => $content,
2134 'source_url' => $url,
2135 'type' => $is_product ? 'product' : 'content',
2136 'last_updated' => time()
2137 )
2138 )
2139 )
2140 );
2141
2142 $response = wp_remote_post($api_endpoint, array(
2143 'headers' => array(
2144 'Api-Key' => $api_key,
2145 'accept' => 'application/json',
2146 'content-type' => 'application/json'
2147 ),
2148 'body' => wp_json_encode($request_body),
2149 'timeout' => 30,
2150 'data_format' => 'body'
2151 ));
2152
2153 if (is_wp_error($response)) {
2154 return array(
2155 'success' => false,
2156 'message' => $response->get_error_message()
2157 );
2158 }
2159
2160 $response_code = wp_remote_retrieve_response_code($response);
2161 if ($response_code !== 200) {
2162 $body = wp_remote_retrieve_body($response);
2163 return array(
2164 'success' => false,
2165 'message' => sprintf(
2166 'Pinecone API error (HTTP %d): %s',
2167 $response_code,
2168 $body
2169 )
2170 );
2171 }
2172
2173 return array(
2174 'success' => true,
2175 'message' => 'Successfully stored in Pinecone'
2176 );
2177 }
2178
2179
2180 // Helper method for WordPress DB storage
2181 private function store_in_wordpress_db($content, $url, $embedding_vector) {
2182 global $wpdb;
2183 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2184
2185 $existing_entry = $wpdb->get_row(
2186 $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url)
2187 );
2188
2189 if ($existing_entry) {
2190 $wpdb->update(
2191 $table_name,
2192 array(
2193 'article_content' => $content,
2194 'embedding_vector' => serialize($embedding_vector),
2195 'timestamp' => current_time('mysql')
2196 ),
2197 array('id' => $existing_entry->id),
2198 array('%s', '%s', '%s')
2199 );
2200 } else {
2201 $wpdb->insert(
2202 $table_name,
2203 array(
2204 'article_content' => $content,
2205 'source_url' => $url,
2206 'embedding_vector' => serialize($embedding_vector),
2207 'timestamp' => current_time('mysql')
2208 ),
2209 array('%s', '%s', '%s', '%s')
2210 );
2211 }
2212 }
2213
2214
2215 public function mxchat_handle_content_submission() {
2216 // Check if the form was submitted and the user has permission.
2217 if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) {
2218 return;
2219 }
2220
2221 // Verify the nonce.
2222 $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : '';
2223 if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) {
2224 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
2225 }
2226
2227 // Sanitize the inputs.
2228 $article_content = sanitize_textarea_field($_POST['article_content']);
2229 $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : '';
2230
2231 // Generate the embedding vector.
2232 $embedding_vector = $this->mxchat_generate_embedding($article_content);
2233 if (!is_array($embedding_vector)) {
2234 set_transient('mxchat_admin_notice_error',
2235 esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'),
2236 30
2237 );
2238 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2239 exit;
2240 }
2241
2242 // Store in the WordPress database.
2243 global $wpdb;
2244 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2245
2246 // Ensure the source_url column exists.
2247 if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') {
2248 $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''"));
2249 }
2250
2251 // Serialize and store the embedding vector.
2252 $embedding_vector_serialized = serialize($embedding_vector);
2253 $inserted = $wpdb->insert(
2254 $table_name,
2255 array(
2256 'article_content' => $article_content,
2257 'embedding_vector' => $embedding_vector_serialized,
2258 'source_url' => $article_url,
2259 ),
2260 array('%s', '%s', '%s')
2261 );
2262
2263 if ($inserted === false) {
2264 set_transient('mxchat_admin_notice_error',
2265 esc_html__('Error inserting content into the database. Please try again.', 'mxchat'),
2266 30
2267 );
2268 } else {
2269 set_transient('mxchat_admin_notice_success',
2270 esc_html__('Content successfully submitted!', 'mxchat'),
2271 30
2272 );
2273 }
2274
2275 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2276 exit;
2277 }
2278
2279 private function is_pdf_url($url, $response) {
2280 $content_type = wp_remote_retrieve_header($response, 'content-type');
2281 $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION));
2282
2283 return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf';
2284 }
2285 private function handle_pdf_for_knowledge_base($pdf_url, $response) {
2286 if (!current_user_can('manage_options')) {
2287 //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat'));
2288 return false;
2289 }
2290
2291 $pdf_url = esc_url_raw($pdf_url);
2292 $upload_dir = wp_upload_dir();
2293
2294 if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
2295 //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error'])));
2296 return false;
2297 }
2298
2299 $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf');
2300 $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename;
2301
2302 $response_body = wp_remote_retrieve_body($response);
2303 if (empty($response_body)) {
2304 //error_log(esc_html__('Empty PDF response body', 'mxchat'));
2305 return false;
2306 }
2307
2308 if (!wp_mkdir_p(dirname($pdf_path))) {
2309 //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path)));
2310 return false;
2311 }
2312
2313 try {
2314 file_put_contents($pdf_path, $response_body);
2315
2316 if (!file_exists($pdf_path)) {
2317 throw new Exception(__('Failed to save PDF file', 'mxchat'));
2318 }
2319
2320 $parser = new \Smalot\PdfParser\Parser();
2321 $pdf = $parser->parseFile($pdf_path);
2322 $total_pages = absint(count($pdf->getPages()));
2323
2324 if ($total_pages < 1) {
2325 throw new Exception(__('Invalid PDF: no pages found', 'mxchat'));
2326 }
2327
2328 wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array(
2329 'pdf_path' => $pdf_path,
2330 'pdf_url' => $pdf_url,
2331 'total_pages' => $total_pages,
2332 'batch_size' => absint(15),
2333 'batch_pause' => absint(10)
2334 ));
2335
2336 $status_data = array(
2337 'total_pages' => $total_pages,
2338 'processed_pages' => 0,
2339 'status' => 'processing',
2340 'last_update' => time()
2341 );
2342
2343 set_transient(
2344 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2345 array_map('sanitize_text_field', $status_data),
2346 DAY_IN_SECONDS
2347 );
2348
2349 return __('scheduled', 'mxchat');
2350
2351 } catch (Exception $e) {
2352 //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage())));
2353 if (file_exists($pdf_path)) {
2354 wp_delete_file($pdf_path);
2355 }
2356 return false;
2357 }
2358 }
2359 public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) {
2360 // Validate inputs
2361 $pdf_path = sanitize_text_field($pdf_path);
2362 $pdf_url = esc_url_raw($pdf_url);
2363 $total_pages = absint($total_pages);
2364 $batch_size = absint($batch_size);
2365 $batch_pause = absint($batch_pause);
2366
2367 try {
2368 if (!file_exists($pdf_path)) {
2369 throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path)));
2370 }
2371
2372 $parser = new \Smalot\PdfParser\Parser();
2373 $pdf = $parser->parseFile($pdf_path);
2374 $pages = $pdf->getPages();
2375
2376 // Get current progress
2377 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2378 $status = get_transient($status_key);
2379
2380 if (!$status || !is_array($status)) {
2381 throw new Exception('Invalid status data retrieved from transient');
2382 }
2383
2384 $start_page = absint($status['processed_pages']);
2385 $end_page = min($start_page + $batch_size, $total_pages);
2386
2387 $instance = new self(); // Create an instance of the class
2388 $options = get_option('mxchat_options');
2389
2390 if (empty($options['api_key'])) {
2391 throw new Exception('API key is missing or invalid');
2392 }
2393
2394 for ($i = $start_page; $i < $end_page; $i++) {
2395 $text = $pages[$i]->getText();
2396
2397 if (empty($text)) {
2398 // Log empty page but continue processing
2399 //error_log(sprintf('[MXCHAT-PDF] Warning: Empty text on page %d of %s', $i + 1, $pdf_url));
2400 continue;
2401 }
2402
2403 $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance
2404
2405 if (empty($sanitized_content)) {
2406 // Log empty sanitized content but continue processing
2407 //error_log(sprintf('[MXCHAT-PDF] Warning: No valid content after sanitization on page %d of %s', $i + 1, $pdf_url));
2408 continue;
2409 }
2410
2411 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance
2412
2413 if (!is_array($embedding_vector)) {
2414 // If embedding generation fails, log error and throw exception
2415 $error_msg = is_string($embedding_vector) ? $embedding_vector : 'Unknown embedding generation error';
2416 //error_log(sprintf('[MXCHAT-PDF] Error generating embedding for page %d: %s', $i + 1, $error_msg));
2417 throw new Exception(sprintf('Failed to generate embedding for page %d: %s', $i + 1, $error_msg));
2418 }
2419
2420 $metadata = array(
2421 'document_type' => 'pdf',
2422 'total_pages' => $total_pages,
2423 'current_page' => $i + 1,
2424 'prev_page' => $i > 0 ? $i : null,
2425 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null,
2426 'source_url' => $pdf_url
2427 );
2428
2429 $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content;
2430 $page_url = esc_url($pdf_url . "#page=" . ($i + 1));
2431
2432 $db_result = MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']);
2433
2434 if (is_wp_error($db_result)) {
2435 throw new Exception(sprintf('Failed to store content in database for page %d: %s',
2436 $i + 1, $db_result->get_error_message()));
2437 }
2438
2439 // Update progress with sanitized data
2440 $status['processed_pages'] = absint($i + 1);
2441 $status['last_update'] = time();
2442 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2443 }
2444
2445 // Schedule next batch if needed
2446 if ($end_page < $total_pages) {
2447 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
2448 'pdf_path' => $pdf_path,
2449 'pdf_url' => $pdf_url,
2450 'total_pages' => $total_pages,
2451 'batch_size' => $batch_size,
2452 'batch_pause' => $batch_pause
2453 ));
2454 } else {
2455 // Processing complete
2456 $status['status'] = 'complete';
2457 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2458 if (file_exists($pdf_path)) {
2459 wp_delete_file($pdf_path);
2460 }
2461 }
2462
2463 } catch (\Exception $e) {
2464 //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage()));
2465
2466 // Get current status to update it
2467 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2468 $status = get_transient($status_key);
2469
2470 if (!$status || !is_array($status)) {
2471 $status = array(
2472 'total_pages' => $total_pages,
2473 'processed_pages' => 0,
2474 'status' => 'error',
2475 'error' => sanitize_text_field($e->getMessage()),
2476 'last_update' => time()
2477 );
2478 } else {
2479 $status['status'] = 'error';
2480 $status['error'] = sanitize_text_field($e->getMessage());
2481 $status['last_update'] = time();
2482 }
2483
2484 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2485
2486 if (file_exists($pdf_path)) {
2487 wp_delete_file($pdf_path);
2488 }
2489 }
2490 }
2491
2492 public function get_pdf_processing_status($pdf_url) {
2493 $pdf_url = esc_url_raw($pdf_url);
2494 $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
2495
2496 if (!$status || !is_array($status)) {
2497 return false;
2498 }
2499
2500 // Check for stalled processing (no updates for 5 minutes)
2501 if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) {
2502 $status['status'] = 'error';
2503 $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat');
2504
2505 // Save the updated status
2506 set_transient(
2507 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2508 array_map('sanitize_text_field', $status),
2509 DAY_IN_SECONDS
2510 );
2511 }
2512
2513 $result = array(
2514 'total_pages' => absint($status['total_pages']),
2515 'processed_pages' => absint($status['processed_pages']),
2516 'percentage' => ($status['total_pages'] > 0)
2517 ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2518 : 0,
2519 'status' => sanitize_text_field($status['status']),
2520 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
2521 );
2522
2523 // Add error message if present
2524 if (isset($status['error']) && !empty($status['error'])) {
2525 $result['error'] = sanitize_text_field($status['error']);
2526 }
2527
2528 return $result;
2529 }
2530
2531
2532 public function mxchat_handle_sitemap_submission() {
2533 // Start logging the submission process
2534 //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
2535
2536 // Check if the form was submitted and verify permissions
2537 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
2538 //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
2539 wp_die(esc_html__('Unauthorized access', 'mxchat'));
2540 }
2541
2542 // Verify nonce
2543 //error_log('[MXCHAT-URL] Verifying nonce');
2544 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
2545
2546 // Validate URL
2547 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
2548 //error_log('[MXCHAT-URL] Error: Empty or missing URL');
2549 set_transient('mxchat_admin_notice_error',
2550 esc_html__('Please provide a valid URL.', 'mxchat'),
2551 30
2552 );
2553 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2554 exit;
2555 }
2556
2557 $submitted_url = esc_url_raw($_POST['sitemap_url']);
2558 //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url);
2559
2560 // Validate API key first
2561 $options = get_option('mxchat_options');
2562 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2563
2564 if (strpos($selected_model, 'voyage') === 0) {
2565 $api_key = $options['voyage_api_key'] ?? '';
2566 $provider_name = 'Voyage AI';
2567 } else {
2568 $api_key = $options['api_key'] ?? '';
2569 $provider_name = 'OpenAI';
2570 }
2571
2572 if (empty($api_key)) {
2573 $error_message = sprintf(
2574 esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
2575 $provider_name
2576 );
2577 //error_log('[MXCHAT-URL] Error: ' . $error_message);
2578 set_transient('mxchat_admin_notice_error', $error_message, 30);
2579 //error_log('[MXCHAT-URL] Set error transient: ' . $error_message);
2580 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2581 exit;
2582 }
2583
2584 //error_log('[MXCHAT-URL] Fetching URL content');
2585 $response = wp_remote_get($submitted_url, array('timeout' => 30));
2586
2587 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
2588 $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
2589 //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message);
2590 set_transient('mxchat_admin_notice_error',
2591 sprintf(
2592 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
2593 esc_html($error_message)
2594 ),
2595 30
2596 );
2597 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2598 exit;
2599 }
2600
2601 $content_type = wp_remote_retrieve_header($response, 'content-type');
2602 //error_log('[MXCHAT-URL] Content type: ' . $content_type);
2603 $body_content = wp_remote_retrieve_body($response);
2604
2605 if (empty($body_content)) {
2606 //error_log('[MXCHAT-URL] Error: Empty response body');
2607 set_transient('mxchat_admin_notice_error',
2608 esc_html__('Empty response received from URL.', 'mxchat'),
2609 30
2610 );
2611 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2612 exit;
2613 }
2614 //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
2615
2616 // Handle PDF URL
2617 if ($this->is_pdf_url($submitted_url, $response)) {
2618 //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
2619 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2620 //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
2621
2622 if ($result === 'scheduled') {
2623 set_transient(
2624 'mxchat_last_pdf_url',
2625 sanitize_text_field($submitted_url),
2626 DAY_IN_SECONDS
2627 );
2628 set_transient('mxchat_admin_notice_info',
2629 esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2630 30
2631 );
2632 } else {
2633 set_transient('mxchat_admin_notice_error',
2634 esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
2635 30
2636 );
2637 }
2638
2639 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2640 exit;
2641 }
2642
2643 // Handle Sitemap XML
2644 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2645 //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
2646 libxml_use_internal_errors(true);
2647 $xml = simplexml_load_string($body_content);
2648 $xml_errors = libxml_get_errors();
2649 libxml_clear_errors();
2650
2651 if ($xml === false || !empty($xml_errors)) {
2652 //error_log('[MXCHAT-URL] Error: Invalid XML format');
2653 if (!empty($xml_errors)) {
2654 foreach ($xml_errors as $error) {
2655 //error_log('[MXCHAT-URL] XML Error: ' . $error->message);
2656 }
2657 }
2658
2659 set_transient('mxchat_admin_notice_error',
2660 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
2661 30
2662 );
2663 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2664 exit;
2665 }
2666
2667 //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
2668 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2669 //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
2670
2671 if ($result === 'scheduled') {
2672 set_transient(
2673 'mxchat_last_sitemap_url',
2674 sanitize_text_field($submitted_url),
2675 DAY_IN_SECONDS
2676 );
2677 set_transient('mxchat_admin_notice_info',
2678 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2679 30
2680 );
2681 } else {
2682 // Return to the admin page without a redirect for better error display
2683 // The error is already stored in the sitemap status transient
2684 set_transient('mxchat_admin_notice_error',
2685 esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
2686 30
2687 );
2688 }
2689
2690 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2691 exit;
2692 }
2693
2694 // Handle Regular URL
2695 // Handle Regular URL
2696 //error_log('[MXCHAT-URL] Processing as regular webpage');
2697 $page_content = $this->mxchat_extract_main_content($body_content);
2698 //error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
2699
2700 $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2701 //error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
2702
2703 if (empty($sanitized_content)) {
2704 //error_log('[MXCHAT-URL] Error: No valid content after sanitization');
2705
2706 // Set both transients - the error notice and the URL status
2707 set_transient('mxchat_admin_notice_error',
2708 esc_html__('No valid content found on the provided URL.', 'mxchat'),
2709 30
2710 );
2711
2712 // Set URL status transient
2713 set_transient('mxchat_single_url_status', [
2714 'url' => $submitted_url,
2715 'timestamp' => current_time('mysql'),
2716 'status' => 'failed',
2717 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat')
2718 ], DAY_IN_SECONDS);
2719
2720 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2721 exit;
2722 }
2723
2724 //error_log('[MXCHAT-URL] Generating embedding for content');
2725 $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2726
2727 // Check if embedding_vector is a string (error message)
2728 if (is_string($embedding_vector)) {
2729 //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector);
2730 $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector);
2731 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2732
2733 // Set both transients
2734 set_transient('mxchat_admin_notice_error', $error_message, 30);
2735
2736 // Set URL status transient
2737 set_transient('mxchat_single_url_status', [
2738 'url' => $submitted_url,
2739 'timestamp' => current_time('mysql'),
2740 'status' => 'failed',
2741 'error' => $error_message
2742 ], DAY_IN_SECONDS);
2743
2744 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2745 exit;
2746 }
2747
2748 if (is_array($embedding_vector)) {
2749 //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
2750
2751 $db_result = MxChat_Utils::submit_content_to_db(
2752 $sanitized_content,
2753 $submitted_url,
2754 $this->options['api_key']
2755 );
2756
2757 if (is_wp_error($db_result)) {
2758 //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message());
2759 $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message());
2760 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2761
2762 // Set both transients
2763 set_transient('mxchat_admin_notice_error', $error_message, 30);
2764
2765 // Set URL status transient
2766 set_transient('mxchat_single_url_status', [
2767 'url' => $submitted_url,
2768 'timestamp' => current_time('mysql'),
2769 'status' => 'failed',
2770 'error' => $error_message
2771 ], DAY_IN_SECONDS);
2772
2773 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2774 exit;
2775 }
2776
2777 //error_log('[MXCHAT-URL] Successfully stored content in database');
2778 $success_message = esc_html__('URL content successfully submitted!', 'mxchat');
2779 //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message);
2780
2781 // Set both transients
2782 set_transient('mxchat_admin_notice_success', $success_message, 30);
2783
2784 // Set URL status transient with success
2785 set_transient('mxchat_single_url_status', [
2786 'url' => $submitted_url,
2787 'timestamp' => current_time('mysql'),
2788 'status' => 'complete',
2789 'content_length' => strlen($sanitized_content),
2790 'embedding_dimensions' => count($embedding_vector)
2791 ], DAY_IN_SECONDS);
2792
2793 } else {
2794 //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector));
2795 $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat');
2796 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2797
2798 // Set both transients
2799 set_transient('mxchat_admin_notice_error', $error_message, 30);
2800
2801 // Set URL status transient
2802 set_transient('mxchat_single_url_status', [
2803 'url' => $submitted_url,
2804 'timestamp' => current_time('mysql'),
2805 'status' => 'failed',
2806 'error' => $error_message
2807 ], DAY_IN_SECONDS);
2808 }
2809
2810 //error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
2811 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2812 exit;
2813 }
2814 /**
2815 * Get the status of the last single URL submission
2816 */
2817 private function get_single_url_status() {
2818 $status = get_transient('mxchat_single_url_status');
2819 if (!$status) {
2820 return null;
2821 }
2822
2823 // Add human-readable time
2824 if (isset($status['timestamp'])) {
2825 $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat');
2826 }
2827
2828 return $status;
2829 }
2830
2831 private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
2832 // Clear any single URL status when starting sitemap processing
2833 delete_transient('mxchat_single_url_status');
2834 if (!current_user_can('manage_options')) {
2835 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
2836 return false;
2837 }
2838
2839 try {
2840 $sitemap_url = esc_url_raw($sitemap_url);
2841
2842 if (!$xml || !is_object($xml)) {
2843 throw new Exception(__('Invalid XML object provided', 'mxchat'));
2844 }
2845
2846 // Add embedding validation before processing
2847 // Test embedding with a small sample text to verify API key is working
2848 $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working.");
2849
2850 // Check if test_result is a string (error message) rather than an array (valid embedding)
2851 if (is_string($test_result)) {
2852 //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result);
2853
2854 // Store the error in the status transient so it can be displayed later
2855 $status_data = array(
2856 'total_urls' => 0,
2857 'processed_urls' => 0,
2858 'status' => 'error',
2859 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result,
2860 'last_update' => time()
2861 );
2862
2863 set_transient(
2864 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2865 array_map('sanitize_text_field', $status_data),
2866 DAY_IN_SECONDS
2867 );
2868
2869 throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result);
2870 }
2871
2872 // Make sure it's an array (valid embedding)
2873 if (!is_array($test_result)) {
2874 //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result));
2875 throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat'));
2876 }
2877
2878 $urls = [];
2879 foreach ($xml->url as $url_element) {
2880 $url = esc_url_raw((string)$url_element->loc);
2881 if ($url) {
2882 $urls[] = $url;
2883 }
2884 }
2885
2886 $total_urls = absint(count($urls));
2887
2888 if ($total_urls < 1) {
2889 throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
2890 }
2891
2892 wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
2893 'urls' => $urls,
2894 'sitemap_url' => $sitemap_url,
2895 'total_urls' => $total_urls,
2896 'batch_size' => absint(10),
2897 'batch_pause' => absint(5)
2898 ));
2899
2900 $status_data = array(
2901 'total_urls' => $total_urls,
2902 'processed_urls' => 0,
2903 'status' => 'processing',
2904 'last_update' => time()
2905 );
2906
2907 set_transient(
2908 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2909 array_map('sanitize_text_field', $status_data),
2910 DAY_IN_SECONDS
2911 );
2912
2913 return __('scheduled', 'mxchat');
2914
2915 } catch (\Exception $e) {
2916 $error_message = $e->getMessage();
2917 //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message)));
2918
2919 // Store the sitemap URL and error in transients so they can be displayed
2920 set_transient(
2921 'mxchat_last_sitemap_url',
2922 sanitize_text_field($sitemap_url),
2923 DAY_IN_SECONDS
2924 );
2925
2926 $status_data = array(
2927 'total_urls' => 0,
2928 'processed_urls' => 0,
2929 'status' => 'error',
2930 'error' => $error_message,
2931 'last_update' => time()
2932 );
2933
2934 set_transient(
2935 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2936 array_map('sanitize_text_field', $status_data),
2937 DAY_IN_SECONDS
2938 );
2939
2940 return $error_message;
2941 }
2942 }
2943
2944 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
2945 // Validate inputs
2946 $sitemap_url = esc_url_raw($sitemap_url);
2947 $total_urls = absint($total_urls);
2948 $batch_size = absint($batch_size);
2949 $batch_pause = absint($batch_pause);
2950
2951 if (!is_array($urls) || empty($urls)) {
2952 return;
2953 }
2954
2955 try {
2956 $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
2957 $status = get_transient($status_key);
2958
2959 if (!$status || !is_array($status)) {
2960 throw new Exception('Invalid status data retrieved from transient');
2961 }
2962
2963 // Initialize failed_urls array if it doesn't exist
2964 if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) {
2965 $status['failed_urls_list'] = [];
2966 }
2967
2968 $start_url = absint($status['processed_urls']);
2969 $end_url = min($start_url + $batch_size, $total_urls);
2970 $instance = new self();
2971
2972 // Track failures in batch
2973 $batch_stats = [
2974 'processed' => 0,
2975 'failed' => 0,
2976 'last_error' => '',
2977 'embedding_errors' => 0 // Track specifically embedding errors
2978 ];
2979
2980 // Check embedding configuration with first URL
2981 if ($start_url === 0) {
2982 $page_url = esc_url_raw($urls[0]);
2983 $page_response = wp_remote_get($page_url);
2984
2985 if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) {
2986 $page_html = wp_remote_retrieve_body($page_response);
2987 $page_content = $instance->mxchat_extract_main_content($page_html);
2988 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
2989
2990 if (!empty($sanitized_content)) {
2991 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2992
2993 // Check if embedding_vector is a string (error message)
2994 if (is_string($embedding_vector)) {
2995 throw new Exception('Embedding generation failed: ' . $embedding_vector);
2996 }
2997
2998 // Make sure it's an array (valid embedding)
2999 if (!is_array($embedding_vector)) {
3000 throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector));
3001 }
3002 }
3003 }
3004 }
3005
3006 for ($i = $start_url; $i < $end_url; $i++) {
3007 $page_url = esc_url_raw($urls[$i]);
3008 $page_response = wp_remote_get($page_url);
3009
3010 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
3011 $batch_stats['failed']++;
3012 $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response);
3013 $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message;
3014
3015 // Add to failed URLs list with error message
3016 $status['failed_urls_list'][] = [
3017 'url' => $page_url,
3018 'error' => $error_message,
3019 'time' => time()
3020 ];
3021
3022 continue;
3023 }
3024
3025 $page_html = wp_remote_retrieve_body($page_response);
3026 $page_content = $instance->mxchat_extract_main_content($page_html);
3027 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
3028
3029 if (!empty($sanitized_content)) {
3030 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
3031
3032 // Check if embedding_vector is a string (error message)
3033 if (is_string($embedding_vector)) {
3034 $batch_stats['failed']++;
3035 $batch_stats['embedding_errors']++;
3036 $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector;
3037
3038 // Add to failed URLs list with error message
3039 $status['failed_urls_list'][] = [
3040 'url' => $page_url,
3041 'error' => 'Embedding error: ' . $embedding_vector,
3042 'time' => time()
3043 ];
3044
3045 // If we have multiple embedding errors, stop processing
3046 if ($batch_stats['embedding_errors'] >= 10) {
3047 throw new Exception('Multiple embedding failures detected: ' . $embedding_vector);
3048 }
3049 continue;
3050 }
3051
3052 // Check if it's an array (valid embedding)
3053 if (is_array($embedding_vector)) {
3054 $options = get_option('mxchat_options');
3055 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
3056
3057 if (is_wp_error($submission_result)) {
3058 $batch_stats['failed']++;
3059 $batch_stats['last_error'] = $submission_result->get_error_message();
3060
3061 // Add to failed URLs list with error message
3062 $status['failed_urls_list'][] = [
3063 'url' => $page_url,
3064 'error' => 'Database submission error: ' . $submission_result->get_error_message(),
3065 'time' => time()
3066 ];
3067
3068 continue;
3069 }
3070
3071 $batch_stats['processed']++;
3072 } else {
3073 $batch_stats['failed']++;
3074 $batch_stats['embedding_errors']++;
3075 $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector);
3076
3077 // Add to failed URLs list with error message
3078 $status['failed_urls_list'][] = [
3079 'url' => $page_url,
3080 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector),
3081 'time' => time()
3082 ];
3083
3084 // If we have multiple embedding errors, stop processing
3085 if ($batch_stats['embedding_errors'] >= 10) {
3086 throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.');
3087 }
3088 }
3089 }
3090
3091 $status['processed_urls'] = absint($i + 1);
3092 $status['last_update'] = time();
3093 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
3094 $status['last_error'] = $batch_stats['last_error'];
3095
3096 // Limit the number of failed URLs we store to prevent transient size issues
3097 if (count($status['failed_urls_list']) > 100) {
3098 $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100);
3099 }
3100
3101 set_transient($status_key, $status, DAY_IN_SECONDS);
3102 }
3103
3104 // If all URLs in this batch failed, stop processing
3105 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
3106 $status['status'] = 'error';
3107 $status['error'] = sprintf(
3108 'Processing stopped: %d consecutive failures. Last error: %s',
3109 $batch_stats['failed'],
3110 $batch_stats['last_error']
3111 );
3112 set_transient($status_key, $status, DAY_IN_SECONDS);
3113 return;
3114 }
3115
3116 if ($end_url < $total_urls) {
3117 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
3118 'urls' => $urls,
3119 'sitemap_url' => $sitemap_url,
3120 'total_urls' => $total_urls,
3121 'batch_size' => $batch_size,
3122 'batch_pause' => $batch_pause,
3123 ));
3124 } else {
3125 $status['status'] = 'complete';
3126 set_transient($status_key, $status, DAY_IN_SECONDS);
3127 }
3128 } catch (\Exception $e) {
3129 $status['status'] = 'error';
3130 $status['error'] = $e->getMessage();
3131 set_transient($status_key, $status, DAY_IN_SECONDS);
3132 }
3133 }
3134
3135 private function mxchat_sanitize_content_for_api($content) {
3136 //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...');
3137
3138 // Remove script, style tags, and HTML comments
3139 $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
3140 $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
3141 $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3142
3143 // Remove all HTML tags and decode HTML entities
3144 $content = wp_strip_all_tags($content);
3145 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3146
3147 // Trim and normalize whitespace
3148 $content = trim(preg_replace('/\s+/', ' ', $content));
3149
3150 // Remove control characters (which can cause database issues)
3151 $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content);
3152
3153 // Remove NULL bytes which can cause database errors
3154 $content = str_replace("\0", "", $content);
3155
3156 // Ensure valid UTF-8 encoding
3157 $content = wp_check_invalid_utf8($content);
3158
3159 // Remove any extremely long strings without spaces (often garbage)
3160 $content = preg_replace('/\S{300,}/', ' ', $content);
3161
3162 // Replace problematic characters that often cause database issues
3163 $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters
3164
3165 // Replace any remaining potentially problematic characters with spaces
3166 $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content);
3167
3168 // Limit to reasonable length if needed
3169 $max_length = 65000; // Just under MySQL TEXT field limit
3170 if (strlen($content) > $max_length) {
3171 $content = substr($content, 0, $max_length);
3172 }
3173
3174 //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...');
3175 return $content;
3176 }
3177 private static function mxchat_extract_main_content($html) {
3178 if (empty($html)) {
3179 return '';
3180 }
3181 try {
3182 $dom = new DOMDocument;
3183 libxml_use_internal_errors(true); // Suppress HTML parsing errors
3184 @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
3185 $xpath = new DOMXPath($dom);
3186
3187 // For debugging purposes
3188 $debugEnabled = false; // Set to true to enable debugging output
3189 $debug = function($message) use ($debugEnabled) {
3190 if ($debugEnabled) {
3191 //error_log('[MXCHAT-DEBUG] ' . $message);
3192 }
3193 };
3194
3195 // Direct targeting for Gerow theme posts
3196 $post_text = $xpath->query('//div[contains(@class, "post-text")]');
3197 if ($post_text && $post_text->length > 0) {
3198 $debug("Found post-text directly");
3199 $content = '';
3200 foreach ($post_text as $node) {
3201 $content .= $dom->saveHTML($node);
3202 }
3203 if (!empty($content)) {
3204 $debug("Returning post-text content");
3205 return $content;
3206 }
3207 }
3208
3209 // Try to get the blog details content which contains the post-text
3210 $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]');
3211 if ($blog_details && $blog_details->length > 0) {
3212 $debug("Found blog-details-content");
3213 $content = '';
3214 foreach ($blog_details as $node) {
3215 $content .= $dom->saveHTML($node);
3216 }
3217 if (!empty($content)) {
3218 $debug("Returning blog-details-content");
3219 return $content;
3220 }
3221 }
3222
3223 // Try to get the article which contains the blog details
3224 $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]');
3225 if ($article && $article->length > 0) {
3226 $debug("Found article with blog-details-wrap");
3227 $content = '';
3228 foreach ($article as $node) {
3229 $content .= $dom->saveHTML($node);
3230 }
3231 if (!empty($content)) {
3232 $debug("Returning article content");
3233 return $content;
3234 }
3235 }
3236
3237 // Try even broader with the blog-item-wrap
3238 $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]');
3239 if ($blog_item && $blog_item->length > 0) {
3240 $debug("Found blog-item-wrap");
3241 $content = '';
3242 foreach ($blog_item as $node) {
3243 $content .= $dom->saveHTML($node);
3244 }
3245 if (!empty($content)) {
3246 $debug("Returning blog-item-wrap content");
3247 return $content;
3248 }
3249 }
3250
3251 // Specific Gerow theme path
3252 $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]');
3253 if ($gerow_path && $gerow_path->length > 0) {
3254 $debug("Found Gerow theme path to post-text");
3255 $content = '';
3256 foreach ($gerow_path as $node) {
3257 $content .= $dom->saveHTML($node);
3258 }
3259 if (!empty($content)) {
3260 $debug("Returning Gerow post-text content");
3261 return $content;
3262 }
3263 }
3264
3265 // Generic blog post selectors
3266 $selectors = [
3267 // Blog post specific selectors
3268 '//div[contains(@class, "post-text")]',
3269 '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]',
3270 '//div[contains(@class, "blog-details-content")]',
3271 '//article[contains(@class, "blog-details-wrap")]',
3272 '//div[contains(@class, "entry-content")]',
3273 '//div[contains(@class, "blog-content")]',
3274 '//div[contains(@class, "blog-item-wrap")]',
3275
3276 // More general content selectors
3277 '//div[contains(@class, "page__content")]',
3278 '//div[contains(@class, "elementor-widget-container")]',
3279 '//div[contains(@class, "elementor-text-editor")]',
3280 '//div[contains(@class, "elementor-widget-text-editor")]',
3281 '//*[contains(@class, "entry-content")]',
3282 '//*[contains(@class, "post-content")]',
3283 '//*[contains(@class, "article-content")]',
3284 '//*[@id="content"]',
3285 '//*[@id="main-content"]',
3286 '//section[contains(@class, "blog-area")]',
3287 '//article',
3288 '//main',
3289 '//div[contains(@class, "content")]'
3290 ];
3291
3292 // First handle Elementor content
3293 $debug("Checking for Elementor content");
3294 $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]');
3295 if ($elementor_widgets && $elementor_widgets->length > 0) {
3296 $debug("Found Elementor widgets");
3297 $combined_content = '';
3298 foreach ($elementor_widgets as $widget) {
3299 $widget_content = $dom->saveHTML($widget);
3300 if (!empty($widget_content)) {
3301 $combined_content .= $widget_content;
3302 }
3303 }
3304 if (!empty($combined_content)) {
3305 $debug("Returning Elementor content");
3306 return $combined_content;
3307 }
3308 }
3309
3310 // Try standard selectors one by one
3311 foreach ($selectors as $selector) {
3312 $debug("Trying selector: " . $selector);
3313 $nodes = $xpath->query($selector);
3314 if ($nodes && $nodes->length > 0) {
3315 $debug("Found matches for selector: " . $selector);
3316 $content = '';
3317 foreach ($nodes as $node) {
3318 $content .= $dom->saveHTML($node);
3319 }
3320 if (!empty($content)) {
3321 $debug("Returning content from selector: " . $selector);
3322 return $content;
3323 }
3324 }
3325 }
3326
3327 // Manual regex fallback for post-text if DOM methods fail
3328 $debug("Trying regex fallback");
3329 if (preg_match('/<div class="post-text">(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) {
3330 $debug("Found post-text via regex");
3331 return '<div class="post-text">' . $matches[1] . '</div>';
3332 }
3333
3334 // Try to extract the blog section as a whole
3335 $blog_section = $xpath->query('//section[contains(@class, "blog-area")]');
3336 if ($blog_section && $blog_section->length > 0) {
3337 $debug("Found blog-area section");
3338 $content = '';
3339 foreach ($blog_section as $node) {
3340 $content .= $dom->saveHTML($node);
3341 }
3342 if (!empty($content)) {
3343 $debug("Returning blog-area section content");
3344 return $content;
3345 }
3346 }
3347
3348 // Fallback: Return the body content if no specific selector matches
3349 $debug("Using body fallback");
3350 $body = $dom->getElementsByTagName('body');
3351 if ($body->length > 0) {
3352 return $dom->saveHTML($body->item(0));
3353 }
3354
3355 // Last resort: return the original HTML
3356 $debug("Returning original HTML");
3357 return $html;
3358 } catch (Exception $e) {
3359 //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage());
3360 return $html; // Return original HTML if parsing fails
3361 } finally {
3362 libxml_clear_errors();
3363 }
3364 }
3365
3366
3367 public function get_sitemap_processing_status($sitemap_url) {
3368 $sitemap_url = esc_url_raw($sitemap_url);
3369 $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
3370
3371 if (!$status || !is_array($status)) {
3372 return false;
3373 }
3374
3375 return array(
3376 'total_urls' => absint($status['total_urls']),
3377 'processed_urls' => absint($status['processed_urls']),
3378 'failed_urls' => absint($status['failed_urls'] ?? 0),
3379 'percentage' => ($status['total_urls'] > 0)
3380 ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
3381 : 0,
3382 'status' => sanitize_text_field($status['status']),
3383 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
3384 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
3385 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '',
3386 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array()
3387 );
3388 }
3389 public function ajax_get_status_updates() {
3390 // Verify the request
3391 check_ajax_referer('mxchat_status_nonce', 'nonce');
3392
3393 // Get the status just like in your admin page
3394 $pdf_url = get_transient('mxchat_last_pdf_url');
3395 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3396 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
3397 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
3398
3399 // Check for true processing status, not just presence of status
3400 $is_active_processing =
3401 ($sitemap_status && $sitemap_status['status'] === 'processing') ||
3402 ($pdf_status && $pdf_status['status'] === 'processing');
3403
3404 // Get single URL status, but only if no sitemap/PDF is processing
3405 $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false;
3406
3407 // Only clear transients for completed processes, not error states
3408 if ($pdf_status && $pdf_status['status'] === 'complete') {
3409 delete_transient('mxchat_last_pdf_url');
3410 $pdf_status = false;
3411 }
3412 if ($sitemap_status && $sitemap_status['status'] === 'complete') {
3413 delete_transient('mxchat_last_sitemap_url');
3414 $sitemap_status = false;
3415 }
3416
3417 // Return JSON response with the status data
3418 wp_send_json(array(
3419 'pdf_status' => $pdf_status,
3420 'sitemap_status' => $sitemap_status,
3421 'single_url_status' => $single_url_status,
3422 'is_processing' => $is_active_processing
3423 ));
3424 }
3425 public function mxchat_stop_processing() {
3426 // Verify permissions
3427 if (!current_user_can('manage_options')) {
3428 wp_die(esc_html__('Unauthorized access', 'mxchat'));
3429 }
3430
3431 // Verify nonce
3432 check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
3433
3434 // Get the last sitemap URL and clear its transient
3435 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3436 if ($sitemap_url) {
3437 delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
3438 delete_transient('mxchat_last_sitemap_url');
3439 }
3440
3441 // Get the last PDF URL and clear its transient
3442 $pdf_url = get_transient('mxchat_last_pdf_url');
3443 if ($pdf_url) {
3444 delete_transient('mxchat_pdf_status_' . md5($pdf_url));
3445 delete_transient('mxchat_last_pdf_url');
3446 }
3447
3448 // Unschedule any pending sitemap events
3449 $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
3450 if ($timestamp) {
3451 wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
3452 }
3453
3454 // Redirect back with a success message
3455 set_transient('mxchat_admin_notice_success',
3456 esc_html__('Processing has been stopped successfully.', 'mxchat'),
3457 30
3458 );
3459 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
3460 exit;
3461 }
3462
3463
3464 public function mxchat_handle_product_change($post_id, $post, $update) {
3465 if ($post->post_type !== 'product') {
3466 return;
3467 }
3468
3469 if ($post->post_status === 'publish') {
3470 add_action('shutdown', function() use ($post_id) {
3471 $product = wc_get_product($post_id);
3472 if ($product) {
3473 $this->mxchat_store_product_embedding($product);
3474 }
3475 });
3476 }
3477 }
3478 private function mxchat_store_product_embedding($product) {
3479 if (!isset($this->options['enable_woocommerce_integration']) ||
3480 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
3481 return;
3482 }
3483
3484 $source_url = get_permalink($product->get_id());
3485
3486 // Build description
3487 $regular_price = $product->get_regular_price();
3488 $sale_price = $product->get_sale_price();
3489 $description = $product->get_description() . "\n\n" .
3490 esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
3491 esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
3492 esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
3493 esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
3494
3495 // Generate embedding vector
3496 $embedding_vector = $this->mxchat_generate_embedding($description);
3497 if (!is_array($embedding_vector)) {
3498 return;
3499 }
3500
3501 // Check for Pinecone addon and its settings
3502 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
3503 $use_pinecone = false;
3504
3505 if ($pinecone_settings && is_array($pinecone_settings)) {
3506 $use_pinecone = (
3507 isset($pinecone_settings['mxchat_use_pinecone']) &&
3508 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
3509 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
3510 !empty($pinecone_settings['mxchat_pinecone_host']) &&
3511 !empty($pinecone_settings['mxchat_pinecone_index']) &&
3512 !empty($pinecone_settings['mxchat_pinecone_environment'])
3513 );
3514 }
3515
3516 if ($use_pinecone) {
3517 $result = $this->store_in_pinecone_main(
3518 $embedding_vector,
3519 $description,
3520 $source_url,
3521 $pinecone_settings['mxchat_pinecone_api_key'],
3522 $pinecone_settings['mxchat_pinecone_environment'],
3523 $pinecone_settings['mxchat_pinecone_index']
3524 );
3525
3526 if (!$result['success']) {
3527 // Fallback to WordPress DB if Pinecone storage fails
3528 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3529 }
3530 } else {
3531 // Use WordPress DB storage
3532 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3533 }
3534 }
3535 public function mxchat_handle_product_delete($post_id) {
3536 if (get_post_type($post_id) !== 'product') {
3537 return;
3538 }
3539
3540 $source_url = get_permalink($post_id);
3541
3542 // Check for Pinecone addon and its settings
3543 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
3544 $use_pinecone = false;
3545
3546 if ($pinecone_settings && is_array($pinecone_settings)) {
3547 $use_pinecone = (
3548 isset($pinecone_settings['mxchat_use_pinecone']) &&
3549 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
3550 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
3551 !empty($pinecone_settings['mxchat_pinecone_host']) &&
3552 !empty($pinecone_settings['mxchat_pinecone_index']) &&
3553 !empty($pinecone_settings['mxchat_pinecone_environment'])
3554 );
3555 }
3556
3557 if ($use_pinecone) {
3558 $this->delete_from_pinecone(
3559 array($source_url),
3560 $pinecone_settings['mxchat_pinecone_api_key'],
3561 $pinecone_settings['mxchat_pinecone_environment'],
3562 $pinecone_settings['mxchat_pinecone_index']
3563 );
3564 } else {
3565 // Fallback to WordPress DB deletion
3566 global $wpdb;
3567 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
3568 $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
3569 }
3570 }
3571
3572
3573 /**
3574 * Delete vectors from Pinecone by source URL
3575 *
3576 * @param array $urls Array of source URLs to delete
3577 * @param string $api_key Pinecone API key
3578 * @param string $environment Pinecone environment
3579 * @param string $index_name Pinecone index name
3580 * @return array Associative array with 'success' boolean and 'message' string
3581 */
3582 private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
3583 // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
3584 $options = get_option('mxchat_pinecone_addon_options');
3585 $host = $options['mxchat_pinecone_host'] ?? '';
3586
3587 if (empty($host)) {
3588 return array(
3589 'success' => false,
3590 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
3591 );
3592 }
3593
3594 // Build API endpoint using the configured host
3595 $api_endpoint = "https://{$host}/vectors/delete";
3596
3597 // Create vector IDs from URLs (matching your store method's ID generation)
3598 $vector_ids = array_map('md5', $urls);
3599
3600 // Prepare the delete request body
3601 $request_body = array(
3602 'ids' => $vector_ids,
3603 'filter' => array(
3604 'source_url' => array(
3605 '$in' => $urls
3606 )
3607 )
3608 );
3609
3610 // Make the deletion request
3611 $response = wp_remote_post($api_endpoint, array(
3612 'headers' => array(
3613 'Api-Key' => $api_key,
3614 'accept' => 'application/json',
3615 'content-type' => 'application/json'
3616 ),
3617 'body' => wp_json_encode($request_body),
3618 'timeout' => 30,
3619 'data_format' => 'body'
3620 ));
3621
3622 // Handle WordPress HTTP API errors
3623 if (is_wp_error($response)) {
3624 return array(
3625 'success' => false,
3626 'message' => $response->get_error_message()
3627 );
3628 }
3629
3630 // Check response status
3631 $response_code = wp_remote_retrieve_response_code($response);
3632 if ($response_code !== 200) {
3633 $body = wp_remote_retrieve_body($response);
3634 return array(
3635 'success' => false,
3636 'message' => sprintf(
3637 'Pinecone API error (HTTP %d): %s',
3638 $response_code,
3639 $body
3640 )
3641 );
3642 }
3643
3644 // Parse response body
3645 $body = wp_remote_retrieve_body($response);
3646 $response_data = json_decode($body, true);
3647
3648 // Final validation of the response
3649 if (json_last_error() !== JSON_ERROR_NONE) {
3650 return array(
3651 'success' => false,
3652 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
3653 );
3654 }
3655
3656 return array(
3657 'success' => true,
3658 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
3659 );
3660 }
3661
3662
3663
3664
3665
3666 public function mxchat_create_activation_page() {
3667 $license_status = get_option('mxchat_license_status', 'inactive');
3668 $license_error = get_option('mxchat_license_error', '');
3669 ?>
3670 <div class="wrap mxchat-admin-activation">
3671 <div class="mxchat-pro-hero">
3672 <h1 class="pro-title">
3673 <span class="pro-gradient-text">Activate</span> MxChat Pro
3674 </h1>
3675 <p class="pro-subtitle">
3676 <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
3677 </p>
3678 </div>
3679
3680 <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
3681 <div class="error notice">
3682 <p><?php echo esc_html($license_error); ?></p>
3683 </div>
3684 <?php endif; ?>
3685
3686 <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
3687 <div class="mxchat-pro-form-container">
3688 <table class="form-table">
3689 <tr valign="top">
3690 <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
3691 <td>
3692 <input type="email" id="mxchat_pro_email" name="mxchat_pro_email" value="<?php echo esc_attr(get_option('mxchat_pro_email')); ?>" class="regular-text mxchat-pro-input" required />
3693 </td>
3694 </tr>
3695 <tr valign="top">
3696 <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
3697 <td>
3698 <input type="text" id="mxchat_activation_key" name="mxchat_activation_key" value="<?php echo esc_attr(get_option('mxchat_activation_key')); ?>" class="regular-text mxchat-pro-input" required />
3699 </td>
3700 </tr>
3701 </table>
3702 <?php if ($license_status !== 'active'): ?>
3703 <div class="mxchat-pro-button-container">
3704 <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
3705 <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
3706 </div>
3707 <?php endif; ?>
3708 </div>
3709 </form>
3710 <!-- License Status Display -->
3711 <div class="mxchat-pro-status">
3712 <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
3713 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
3714 <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
3715 </span>
3716 </h3>
3717 </div>
3718
3719 </div>
3720 <?php
3721 }
3722
3723 public function mxchat_actions_page_html() {
3724 if (!current_user_can('manage_options')) {
3725 return;
3726 }
3727
3728 // Keep existing data fetching logic
3729 global $wpdb;
3730 $table_name = $wpdb->prefix . 'mxchat_intents';
3731 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
3732 $per_page = 20;
3733 $offset = ($page - 1) * $per_page;
3734
3735 // Success message
3736 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
3737 echo '<div class="notice notice-success is-dismissible"><p>' .
3738 esc_html__('Action updated successfully.', 'mxchat') .
3739 '</p></div>';
3740 }
3741
3742 // Filtering logic
3743 $where = '1=1';
3744 $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
3745 $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
3746
3747 if ($search_term) {
3748 $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
3749 $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
3750 $search_term_like, $search_term_like);
3751 }
3752
3753 if ($callback_filter) {
3754 $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
3755 }
3756
3757 // Pagination
3758 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
3759 $total_pages = ceil($total_intents / $per_page);
3760
3761 // Get intents (now called actions)
3762 $actions = $wpdb->get_results($wpdb->prepare(
3763 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
3764 $per_page, $offset
3765 ));
3766
3767 // Get callbacks
3768 $available_callbacks = $this->mxchat_get_available_callbacks();
3769
3770 ?>
3771 <div class="wrap mxchat-wrapper">
3772 <!-- Hero Section -->
3773 <div class="mxchat-hero">
3774 <h1 class="mxchat-main-title">
3775 <span class="mxchat-gradient-text">Actions</span> Manager
3776 </h1>
3777 <p class="mxchat-hero-subtitle">
3778 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
3779 </p>
3780 </div>
3781
3782 <!-- Actions Header with Search and Filter -->
3783 <div class="mxchat-actions-header">
3784 <div class="mxchat-actions-filters">
3785 <form method="get" class="mxchat-search-form">
3786 <input type="hidden" name="page" value="mxchat-actions">
3787 <div class="mxchat-search-group">
3788 <span class="dashicons dashicons-search"></span>
3789 <input type="text" name="s" class="mxchat-search-input"
3790 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3791 value="<?php echo esc_attr($search_term); ?>">
3792 </div>
3793 <select name="callback_filter" class="mxchat-action-filter">
3794 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3795 <?php foreach ($available_callbacks as $function => $callback_data) :
3796 $label = $callback_data['label']; ?>
3797 <option value="<?php echo esc_attr($function); ?>"
3798 <?php selected($callback_filter, $function); ?>>
3799 <?php echo esc_html($label); ?>
3800 </option>
3801 <?php endforeach; ?>
3802 </select>
3803 <button type="submit" class="mxchat-button-secondary">
3804 <?php esc_html_e('Filter', 'mxchat'); ?>
3805 </button>
3806 </form>
3807 </div>
3808 <div class="mxchat-actions-controls">
3809 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3810 <span class="dashicons dashicons-plus-alt"></span>
3811 <?php esc_html_e('Add New Action', 'mxchat'); ?>
3812 </button>
3813 </div>
3814 </div>
3815
3816 <!-- Actions Grid Layout - All actions in a single grid -->
3817 <div class="mxchat-actions-grid">
3818 <div class="mxchat-cards-container">
3819 <?php if (!empty($actions)) : ?>
3820 <?php foreach ($actions as $action) :
3821 $callback_function = $action->callback_function;
3822 $callback_label = isset($available_callbacks[$callback_function]['label'])
3823 ? $available_callbacks[$callback_function]['label']
3824 : $callback_function;
3825 $threshold_value = isset($action->similarity_threshold)
3826 ? round($action->similarity_threshold * 100)
3827 : 85;
3828
3829 // Check if this is a form action
3830 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3831
3832 // Get action status (enabled/disabled) - default to true if column doesn't exist
3833 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3834 ?>
3835 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3836 <div class="mxchat-card-header">
3837 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3838 <div class="mxchat-card-toggle">
3839 <label class="mxchat-switch">
3840 <input type="checkbox" class="mxchat-action-toggle"
3841 data-action-id="<?php echo esc_attr($action->id); ?>"
3842 <?php checked($is_enabled); ?>>
3843 <span class="mxchat-slider round"></span>
3844 </label>
3845 </div>
3846 </div>
3847
3848 <div class="mxchat-card-body">
3849 <div class="mxchat-card-description">
3850 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3851 <?php echo esc_html($callback_label); ?>
3852 </div>
3853
3854 <div class="mxchat-card-phrases">
3855 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3856 <div class="mxchat-phrases-preview">
3857 <?php
3858 // Check if the helper function exists, otherwise use a simple substring
3859 if (method_exists($this, 'get_trimmed_phrases')) {
3860 echo esc_html($this->get_trimmed_phrases($action->phrases));
3861 } else {
3862 echo esc_html(strlen($action->phrases) > 100 ?
3863 substr($action->phrases, 0, 97) . '...' :
3864 $action->phrases);
3865 }
3866 ?>
3867 </div>
3868 </div>
3869
3870 <div class="mxchat-threshold-control">
3871 <div class="mxchat-threshold-label">
3872 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3873 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3874 </div>
3875 </div>
3876 </div>
3877
3878 <div class="mxchat-card-footer">
3879 <?php if (!$is_form_action) : ?>
3880 <button type="button"
3881 class="mxchat-button-secondary mxchat-edit-button"
3882 data-action-id="<?php echo esc_attr($action->id); ?>"
3883 data-phrases="<?php echo esc_attr($action->phrases); ?>"
3884 data-label="<?php echo esc_attr($action->intent_label); ?>"
3885 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3886 data-callback-function="<?php echo esc_attr($action->callback_function); ?>">
3887 <span class="dashicons dashicons-edit"></span>
3888 <?php esc_html_e('Edit', 'mxchat'); ?>
3889 </button>
3890 <form method="post"
3891 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3892 class="mxchat-delete-form"
3893 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3894 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3895 <input type="hidden" name="action" value="mxchat_delete_intent">
3896 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3897 <button type="submit" class="mxchat-button-text mxchat-delete-button">
3898 <span class="dashicons dashicons-trash"></span>
3899 <?php esc_html_e('Delete', 'mxchat'); ?>
3900 </button>
3901 </form>
3902 <?php else :
3903 preg_match('/Form (\d+)/', $action->intent_label, $matches);
3904 $form_id = isset($matches[1]) ? $matches[1] : '';
3905 ?>
3906 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3907 class="mxchat-button-primary">
3908 <span class="dashicons dashicons-feedback"></span>
3909 <?php esc_html_e('Edit Form', 'mxchat'); ?>
3910 </a>
3911 <?php endif; ?>
3912 </div>
3913 </div>
3914 <?php endforeach; ?>
3915 <?php else : ?>
3916 <!-- If no actions found -->
3917 <div class="mxchat-no-actions">
3918 <div class="mxchat-empty-state">
3919 <span class="dashicons dashicons-format-chat"></span>
3920 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
3921 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
3922 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
3923 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
3924 </button>
3925 </div>
3926 </div>
3927 <?php endif; ?>
3928 </div>
3929 </div>
3930
3931 <?php if ($total_pages > 1) : ?>
3932 <div class="mxchat-pagination">
3933 <?php
3934 echo paginate_links(array(
3935 'base' => add_query_arg('paged', '%#%'),
3936 'format' => '',
3937 'prev_text' => __('&laquo; Previous', 'mxchat'),
3938 'next_text' => __('Next &raquo;', 'mxchat'),
3939 'total' => $total_pages,
3940 'current' => $page
3941 ));
3942 ?>
3943 </div>
3944 <?php endif; ?>
3945
3946 <!-- Add/Edit Action Modal with Step-Based Approach -->
3947 <!-- Complete Modal HTML with Defined Groups Variable -->
3948 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
3949 <div class="mxchat-modal-content">
3950 <span class="mxchat-modal-close">&times;</span>
3951
3952 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3953 <!-- Dynamic nonce field -->
3954 <div id="action-nonce-container">
3955 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
3956 </div>
3957 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
3958 <input type="hidden" name="intent_id" id="edit_action_id" value="">
3959 <input type="hidden" name="callback_function" id="callback_function" value="">
3960
3961 <!-- Step 1: Action Type Selection -->
3962 <div id="mxchat-action-step-1" class="mxchat-action-step active">
3963 <div class="mxchat-step-indicator">
3964 <div class="mxchat-step-number">1</div>
3965 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
3966 </div>
3967
3968 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
3969 <div class="mxchat-action-type-search">
3970 <span class="dashicons dashicons-search"></span>
3971 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
3972 </div>
3973
3974 <?php
3975 // Get the callbacks - IMPORTANT: Define the $groups variable here
3976 $groups = $this->mxchat_get_available_callbacks(true, true);
3977 ?>
3978
3979 <div class="mxchat-action-type-categories">
3980 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
3981 <?php
3982 // Get unique categories from the defined groups
3983 foreach ($groups as $group_label => $group_callbacks) :
3984 $category_slug = sanitize_title($group_label);
3985 ?>
3986 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
3987 <?php endforeach; ?>
3988 </div>
3989
3990 <div class="mxchat-action-types-grid">
3991 <?php
3992 // Generate action cards from available callbacks
3993 foreach ($groups as $group_label => $group_callbacks) :
3994 $category_slug = sanitize_title($group_label);
3995
3996 foreach ($group_callbacks as $function => $data) :
3997 $label = $data['label'];
3998 $pro_only = $data['pro_only'];
3999 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4000 $description = isset($data['description']) ? $data['description'] : '';
4001 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4002 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4003 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4004
4005 // Determine card status and styling
4006 $card_class = 'mxchat-action-type-card';
4007 $icon_class = 'mxchat-action-type-icon';
4008 $status_badge = '';
4009
4010 if ($pro_only && !$this->is_activated) {
4011 // Pro feature but no Pro license
4012 $icon_class .= ' pro-feature';
4013 $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4014 }
4015
4016 if ($is_addon && !$is_installed) {
4017 // Add-on not installed
4018 $card_class .= ' not-installed';
4019 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4020 }
4021
4022 // Default description if none provided
4023 if (empty($description)) {
4024 $description = sprintf(
4025 esc_html__('Use the %s action in your chatbot', 'mxchat'),
4026 $label
4027 );
4028 }
4029 ?>
4030 <div class="<?php echo esc_attr($card_class); ?>"
4031 data-category="<?php echo esc_attr($category_slug); ?>"
4032 data-value="<?php echo esc_attr($function); ?>"
4033 data-label="<?php echo esc_attr($label); ?>"
4034 data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4035 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4036 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4037 <div class="<?php echo esc_attr($icon_class); ?>">
4038 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4039 </div>
4040 <div class="mxchat-action-type-info">
4041 <h4><?php echo esc_html($label); ?></h4>
4042 <p><?php echo esc_html($description); ?></p>
4043 <?php if (!empty($status_badge)) : ?>
4044 <?php echo $status_badge; ?>
4045 <?php endif; ?>
4046
4047 <?php if ($is_addon && !$is_installed) : ?>
4048 <div class="mxchat-addon-info">
4049 <?php echo esc_html(sprintf(
4050 __('Requires %s', 'mxchat'),
4051 $addon_name
4052 )); ?>
4053 </div>
4054 <?php endif; ?>
4055 </div>
4056 </div>
4057 <?php endforeach;
4058 endforeach; ?>
4059 </div>
4060 </div>
4061
4062 <div class="mxchat-modal-actions">
4063 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4064 <?php esc_html_e('Cancel', 'mxchat'); ?>
4065 </button>
4066 </div>
4067 </div>
4068
4069 <!-- Step 2: Action Configuration -->
4070 <div id="mxchat-action-step-2" class="mxchat-action-step">
4071 <div class="mxchat-step-indicator">
4072 <div class="mxchat-step-number">2</div>
4073 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4074 </div>
4075
4076 <div class="mxchat-selected-action">
4077 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4078 <span class="dashicons dashicons-arrow-left-alt"></span>
4079 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4080 </button>
4081 <div class="mxchat-selected-action-info">
4082 <div id="selected-action-icon" class="mxchat-action-type-icon">
4083 <span class="dashicons dashicons-admin-generic"></span>
4084 </div>
4085 <div class="mxchat-selected-action-details">
4086 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4087 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4088 </div>
4089 </div>
4090 </div>
4091
4092 <div class="mxchat-form-group">
4093 <label for="intent_label">
4094 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4095 </label>
4096 <input name="intent_label" type="text" id="intent_label" required
4097 class="mxchat-intent-input"
4098 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4099 </div>
4100
4101 <div class="mxchat-form-group">
4102 <label for="phrases">
4103 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4104 </label>
4105 <textarea name="phrases" id="action_phrases" rows="5" required
4106 class="mxchat-intent-textarea"
4107 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4108 </div>
4109
4110 <div class="mxchat-form-group">
4111 <label for="similarity_threshold">
4112 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4113 <span class="mxchat-threshold-value-display">85%</span>
4114 </label>
4115 <div class="mxchat-slider-group modal-slider">
4116 <input type="range"
4117 name="similarity_threshold"
4118 id="similarity_threshold"
4119 min="70"
4120 max="95"
4121 value="85"
4122 class="mxchat-intent-slider"
4123 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4124 </div>
4125 <div class="mxchat-threshold-hint">
4126 <?php esc_html_e('Lower values make the action trigger more easily. Higher values require more exact matches.', 'mxchat'); ?>
4127 </div>
4128 </div>
4129
4130 <div class="mxchat-modal-actions">
4131 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4132 <?php esc_html_e('Cancel', 'mxchat'); ?>
4133 </button>
4134 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4135 <?php esc_html_e('Save Action', 'mxchat'); ?>
4136 </button>
4137 </div>
4138 </div>
4139 </form>
4140 </div>
4141 </div>
4142
4143 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4144 <div class="mxchat-action-loading-spinner"></div>
4145 <div class="mxchat-action-loading-text">
4146 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4147 </div>
4148 </div>
4149 </div><!-- .mxchat-wrapper -->
4150 <?php
4151 }
4152
4153 /**
4154 * Helper method to trim phrases for display
4155 * Adding this in case it doesn't exist in your class
4156 */
4157 private function get_trimmed_phrases($phrases, $max_length = 100) {
4158 if (strlen($phrases) <= $max_length) {
4159 return $phrases;
4160 }
4161
4162 $trimmed = substr($phrases, 0, $max_length);
4163 $last_comma = strrpos($trimmed, ',');
4164
4165 if ($last_comma !== false) {
4166 $trimmed = substr($trimmed, 0, $last_comma);
4167 }
4168
4169 return $trimmed . '...';
4170 }
4171
4172 /**
4173 * AJAX handler for toggling an action on/off
4174 */
4175 public function mxchat_toggle_action() {
4176 // Check nonce
4177 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
4178 wp_send_json_error(array('message' => 'Security check failed'));
4179 return;
4180 }
4181
4182 // Check permissions
4183 if (!current_user_can('manage_options')) {
4184 wp_send_json_error(array('message' => 'Permission denied'));
4185 return;
4186 }
4187
4188 // Validate params
4189 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4190 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
4191
4192 if (!$intent_id) {
4193 wp_send_json_error(array('message' => 'Invalid action ID'));
4194 return;
4195 }
4196
4197 // Update the intent/action status in the database
4198 global $wpdb;
4199 $table_name = $wpdb->prefix . 'mxchat_intents';
4200
4201 // Using the 'enabled' field - add this field if it doesn't exist
4202 $result = $wpdb->update(
4203 $table_name,
4204 array('enabled' => $enabled ? 1 : 0),
4205 array('id' => $intent_id),
4206 array('%d'),
4207 array('%d')
4208 );
4209
4210 if ($result === false) {
4211 wp_send_json_error(array('message' => 'Database error'));
4212 return;
4213 }
4214
4215 wp_send_json_success();
4216 }
4217
4218 /**
4219 * Add the 'enabled' column to the intents table if it doesn't exist
4220 * Call this during plugin activation or update
4221 */
4222 public function mxchat_add_enabled_column_to_intents() {
4223 global $wpdb;
4224 $table_name = $wpdb->prefix . 'mxchat_intents';
4225
4226 // Check if the column already exists
4227 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
4228
4229 if (empty($columns)) {
4230 // Add the column with default value of 1 (enabled)
4231 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
4232 }
4233 }
4234
4235 /**
4236 * Handle embedding generation errors using existing admin notice system
4237 *
4238 * @param string $message Error message to display
4239 * @param bool $redirect Whether to redirect back to the actions page
4240 * @return void
4241 */
4242 private function handle_embedding_error($message, $redirect = true) {
4243 // Store the error message in the existing transient
4244 set_transient('mxchat_admin_notice_error', $message, 60);
4245
4246 if ($redirect) {
4247 // Redirect back to the actions page
4248 $redirect_url = add_query_arg(
4249 array(
4250 'page' => 'mxchat-actions'
4251 ),
4252 admin_url('admin.php')
4253 );
4254 wp_safe_redirect($redirect_url);
4255 exit;
4256 }
4257 }
4258
4259 /**
4260 * Handle editing of intent phrases - with improved error handling
4261 *
4262 * @since 1.0.0
4263 * @return void
4264 */
4265 public function mxchat_handle_edit_intent() {
4266 // Security checks (nonce and permissions)
4267 if (!current_user_can('manage_options')) {
4268 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4269 }
4270 check_admin_referer('mxchat_edit_intent');
4271
4272 // Get POST data
4273 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
4274 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4275 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4276 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
4277 $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95
4278
4279 // Validate inputs
4280 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4281 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4282 return;
4283 }
4284
4285 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
4286 if (empty($phrases_array)) {
4287 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4288 return;
4289 }
4290
4291 // Generate embeddings with improved error handling
4292 $vectors = [];
4293 $failed_phrases = [];
4294
4295 foreach ($phrases_array as $phrase) {
4296 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
4297 if (is_array($embedding_vector)) {
4298 $vectors[] = $embedding_vector;
4299 } else {
4300 $failed_phrases[] = $phrase;
4301 }
4302 }
4303
4304 if (!empty($failed_phrases)) {
4305 $this->handle_embedding_error(
4306 sprintf(
4307 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4308 implode(', ', $failed_phrases)
4309 )
4310 );
4311 return;
4312 }
4313
4314 if (empty($vectors)) {
4315 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4316 return;
4317 }
4318
4319 $combined_vector = $this->mxchat_average_vectors($vectors);
4320 $serialized_vector = maybe_serialize($combined_vector);
4321
4322 // Update the database
4323 global $wpdb;
4324 $table_name = $wpdb->prefix . 'mxchat_intents';
4325
4326 $result = $wpdb->update(
4327 $table_name,
4328 array(
4329 'intent_label' => $intent_label,
4330 'phrases' => implode(', ', $phrases_array),
4331 'embedding_vector' => $serialized_vector,
4332 'similarity_threshold' => $similarity_threshold
4333 ),
4334 array('id' => $intent_id),
4335 array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
4336 array('%d') // Where format: integer
4337 );
4338
4339 if (false === $result) {
4340 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
4341 return;
4342 }
4343
4344 // Set success message and redirect
4345 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
4346
4347 $redirect_url = add_query_arg(
4348 array(
4349 'page' => 'mxchat-actions'
4350 ),
4351 admin_url('admin.php')
4352 );
4353 wp_safe_redirect($redirect_url);
4354 exit;
4355 }
4356
4357 /**
4358 * Handle adding new intent - with improved error handling
4359 *
4360 * @return void
4361 */
4362 public function mxchat_handle_add_intent() {
4363 if (!current_user_can('manage_options')) {
4364 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4365 }
4366
4367 check_admin_referer('mxchat_add_intent_nonce');
4368
4369 global $wpdb;
4370 $table_name = $wpdb->prefix . 'mxchat_intents';
4371
4372 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4373 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4374 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4375 $default_threshold = 0.85;
4376
4377 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
4378 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4379 return;
4380 }
4381
4382 $available_callbacks = $this->mxchat_get_available_callbacks();
4383
4384 if (!array_key_exists($callback_function, $available_callbacks)) {
4385 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4386 return;
4387 }
4388
4389 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
4390 if ($is_pro_only && !$this->is_activated) {
4391 $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4392 return;
4393 }
4394
4395 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
4396
4397 if (empty($phrases_array)) {
4398 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4399 return;
4400 }
4401
4402 // Generate embeddings with improved error handling
4403 $vectors = [];
4404 $failed_phrases = [];
4405
4406 foreach ($phrases_array as $phrase) {
4407 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
4408 if (is_array($embedding_vector)) {
4409 $vectors[] = $embedding_vector;
4410 } else {
4411 $failed_phrases[] = $phrase;
4412 }
4413 }
4414
4415 if (!empty($failed_phrases)) {
4416 $this->handle_embedding_error(
4417 sprintf(
4418 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4419 implode(', ', $failed_phrases)
4420 )
4421 );
4422 return;
4423 }
4424
4425 if (empty($vectors)) {
4426 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4427 return;
4428 }
4429
4430 $combined_vector = $this->mxchat_average_vectors($vectors);
4431 $serialized_vector = maybe_serialize($combined_vector);
4432
4433 $result = $wpdb->insert($table_name, [
4434 'intent_label' => $intent_label,
4435 'phrases' => implode(', ', $phrases_array),
4436 'embedding_vector' => $serialized_vector,
4437 'callback_function' => $callback_function,
4438 'similarity_threshold' => $default_threshold,
4439 ]);
4440
4441 if ($result === false) {
4442 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
4443 return;
4444 }
4445
4446 // Set success message
4447 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
4448
4449 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4450 exit;
4451 }
4452
4453
4454 /**
4455 * Update intent threshold with AJAX support
4456 */
4457 public function mxchat_update_intent_threshold() {
4458 // Check permissions
4459 if (!current_user_can('manage_options')) {
4460 if (wp_doing_ajax()) {
4461 wp_send_json_error(array('message' => 'Unauthorized user'));
4462 return;
4463 }
4464 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4465 }
4466
4467 // Verify nonce
4468 check_admin_referer('mxchat_update_intent_threshold_nonce');
4469
4470 // Process the update if we have valid data
4471 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
4472 global $wpdb;
4473 $table_name = $wpdb->prefix . 'mxchat_intents';
4474 $intent_id = intval($_POST['intent_id']);
4475 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
4476 $similarity_threshold = $threshold_percentage / 100;
4477
4478 $result = $wpdb->update(
4479 $table_name,
4480 ['similarity_threshold' => $similarity_threshold],
4481 ['id' => $intent_id],
4482 ['%f'],
4483 ['%d']
4484 );
4485
4486 // Handle AJAX requests
4487 if (wp_doing_ajax()) {
4488 if ($result === false) {
4489 wp_send_json_error(array('message' => 'Failed to update threshold'));
4490 } else {
4491 wp_send_json_success(array('threshold' => $threshold_percentage));
4492 }
4493 return;
4494 }
4495 }
4496
4497 // Redirect for regular form submissions
4498 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
4499 exit;
4500 }
4501
4502
4503
4504 /**
4505 * Enhanced get_available_callbacks function with form action exclusion
4506 *
4507 * @param bool $grouped Whether to return callbacks grouped by category
4508 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4509 * @return array Callbacks data with icons, descriptions and availability status
4510 */
4511 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4512 // Load WordPress plugin functions if needed
4513 if (!function_exists('get_plugins')) {
4514 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4515 }
4516
4517 // Get active plugins
4518 $active_plugins = get_option('active_plugins', array());
4519
4520 // Functions to exclude from the action selector
4521 $excluded_functions = array(
4522 'mxchat_handle_form_collection' // Exclude form actions
4523 );
4524
4525 // Define add-on plugin files and their corresponding action functions
4526 $addon_plugins = array(
4527 'mxchat-woo/mxchat-woo.php' => array(
4528 'functions' => array(
4529 'mxchat_handle_product_recommendations',
4530 'mxchat_handle_order_history',
4531 'mxchat_show_product_card',
4532 'mxchat_add_to_cart',
4533 'mxchat_checkout_redirect'
4534 ),
4535 'name' => __('WooCommerce Add-on', 'mxchat'),
4536 'pro_required' => true
4537 ),
4538 'mxchat-perplexity/mxchat-perplexity.php' => array(
4539 'functions' => array('mxchat_perplexity_research'),
4540 'name' => __('Perplexity Add-on', 'mxchat'),
4541 'pro_required' => true
4542 ),
4543 // Add other add-ons and their functions here
4544 );
4545
4546 // Get the functions that are provided by active add-ons
4547 $addon_provided_functions = array();
4548 $addon_function_mapping = array(); // Maps functions to their add-on info
4549
4550 // Check which add-ons are active
4551 foreach ($addon_plugins as $plugin_file => $addon_info) {
4552 $is_active = in_array($plugin_file, $active_plugins);
4553
4554 // For each function in this addon
4555 foreach ($addon_info['functions'] as $function) {
4556 // Consider a function installed only if:
4557 // 1. The add-on is active AND
4558 // 2. Either it doesn't require Pro OR Pro is activated
4559 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4560
4561 // If the add-on is installed, mark this function as provided by an add-on
4562 if ($is_installed) {
4563 $addon_provided_functions[] = $function;
4564 }
4565
4566 // Store addon info for this function regardless of installation status
4567 $addon_function_mapping[$function] = array(
4568 'addon' => basename(dirname($plugin_file)),
4569 'addon_name' => $addon_info['name'],
4570 'pro_required' => $addon_info['pro_required'],
4571 'is_active' => $is_active,
4572 'is_installed' => $is_installed
4573 );
4574 }
4575 }
4576
4577 // Core callbacks - always available in the base plugin
4578 $core_callbacks = array(
4579 'mxchat_handle_email_capture' => array(
4580 'label' => __('Loops Email Capture', 'mxchat'),
4581 'pro_only' => false,
4582 'group' => __('Customer Engagement', 'mxchat'),
4583 'icon' => 'email-alt',
4584 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4585 'addon' => false, // Not from an add-on
4586 'installed' => true // Always installed with base plugin
4587 ),
4588 'mxchat_handle_search_request' => array(
4589 'label' => __('Brave Web Search', 'mxchat'),
4590 'pro_only' => false,
4591 'group' => __('Search Features', 'mxchat'),
4592 'icon' => 'search',
4593 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4594 'addon' => false,
4595 'installed' => true
4596 ),
4597 'mxchat_handle_image_search_request' => array(
4598 'label' => __('Brave Image Search', 'mxchat'),
4599 'pro_only' => false,
4600 'group' => __('Search Features', 'mxchat'),
4601 'icon' => 'format-image',
4602 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4603 'addon' => false,
4604 'installed' => true
4605 ),
4606 // Pro core features - check is_activated property
4607 'mxchat_generate_image' => array(
4608 'label' => __('Generate Image', 'mxchat'),
4609 'pro_only' => false,
4610 'group' => __('Other Features', 'mxchat'),
4611 'icon' => 'art',
4612 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
4613 'addon' => false,
4614 'installed' => $this->is_activated
4615 ),
4616 'mxchat_handle_pdf_discussion' => array(
4617 'label' => __('Chat with PDF', 'mxchat'),
4618 'pro_only' => true,
4619 'group' => __('Other Features', 'mxchat'),
4620 'icon' => 'media-document',
4621 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4622 'addon' => false,
4623 'installed' => $this->is_activated
4624 ),
4625 'mxchat_live_agent_handover' => array(
4626 'label' => __('Slack Live Agent', 'mxchat'),
4627 'pro_only' => true,
4628 'group' => __('Customer Engagement', 'mxchat'),
4629 'icon' => 'admin-users',
4630 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4631 'addon' => false,
4632 'installed' => $this->is_activated
4633 ),
4634 'mxchat_handle_switch_to_chatbot_intent' => array(
4635 'label' => __('Back to Chatbot', 'mxchat'),
4636 'pro_only' => true,
4637 'group' => __('Customer Engagement', 'mxchat'),
4638 'icon' => 'backup',
4639 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4640 'addon' => false,
4641 'installed' => $this->is_activated
4642 ),
4643 );
4644
4645 // Add-on callbacks with placeholders - only include if the add-on is NOT active
4646 $addon_callbacks = array(
4647 // WooCommerce Add-on
4648 'mxchat_handle_product_recommendations' => array(
4649 'label' => __('Product Recommendations', 'mxchat'),
4650 'pro_only' => true,
4651 'group' => __('WooCommerce Features', 'mxchat'),
4652 'icon' => 'cart',
4653 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4654 ),
4655 'mxchat_handle_order_history' => array(
4656 'label' => __('Order History', 'mxchat'),
4657 'pro_only' => true,
4658 'group' => __('WooCommerce Features', 'mxchat'),
4659 'icon' => 'clipboard',
4660 'description' => __('Allow customers to check their order status', 'mxchat'),
4661 ),
4662 'mxchat_show_product_card' => array(
4663 'label' => __('Show Product Card', 'mxchat'),
4664 'pro_only' => true,
4665 'group' => __('WooCommerce Features', 'mxchat'),
4666 'icon' => 'products',
4667 'description' => __('Display product information in the chat', 'mxchat'),
4668 ),
4669 'mxchat_add_to_cart' => array(
4670 'label' => __('Add to Cart', 'mxchat'),
4671 'pro_only' => true,
4672 'group' => __('WooCommerce Features', 'mxchat'),
4673 'icon' => 'plus-alt',
4674 'description' => __('Add products to cart directly from chat', 'mxchat'),
4675 ),
4676 'mxchat_checkout_redirect' => array(
4677 'label' => __('Proceed to Checkout', 'mxchat'),
4678 'pro_only' => true,
4679 'group' => __('WooCommerce Features', 'mxchat'),
4680 'icon' => 'arrow-right-alt',
4681 'description' => __('Redirect customer to checkout page', 'mxchat'),
4682 ),
4683
4684 // Perplexity Add-on
4685 'mxchat_perplexity_research' => array(
4686 'label' => __('Perplexity Research', 'mxchat'),
4687 'pro_only' => true,
4688 'group' => __('Search Features', 'mxchat'),
4689 'icon' => 'book-alt',
4690 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4691 ),
4692 );
4693
4694 // Enhance add-on callbacks with installation status and addon info
4695 foreach ($addon_callbacks as $function => $data) {
4696 if (isset($addon_function_mapping[$function])) {
4697 $addon_info = $addon_function_mapping[$function];
4698
4699 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4700 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4701 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4702
4703 // Set pro_only based on add-on configuration
4704 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4705 } else {
4706 $addon_callbacks[$function]['addon'] = 'unknown';
4707 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4708 $addon_callbacks[$function]['installed'] = false;
4709 }
4710 }
4711
4712 // Initialize callbacks with core features
4713 $callbacks = $core_callbacks;
4714
4715 // Get callbacks from active add-ons
4716 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4717
4718 // Add placeholder callbacks only for add-ons that aren't active
4719 if ($include_all) {
4720 foreach ($addon_callbacks as $function => $data) {
4721 // Skip placeholders for functions provided by active add-ons
4722 if (in_array($function, $addon_provided_functions)) {
4723 continue;
4724 }
4725
4726 // Skip excluded functions
4727 if (in_array($function, $excluded_functions)) {
4728 continue;
4729 }
4730
4731 // Add the placeholder
4732 $callbacks[$function] = $data;
4733 }
4734 }
4735
4736 // Add callbacks from active add-ons (will override placeholders)
4737 foreach ($active_addon_callbacks as $function => $data) {
4738 // Skip excluded functions
4739 if (in_array($function, $excluded_functions)) {
4740 continue;
4741 }
4742
4743 // Always include callbacks from add-ons
4744 $callbacks[$function] = $data;
4745
4746 // Ensure they have the proper add-on info
4747 if (isset($addon_function_mapping[$function])) {
4748 $addon_info = $addon_function_mapping[$function];
4749 $callbacks[$function]['addon'] = $addon_info['addon'];
4750 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4751 $callbacks[$function]['installed'] = $addon_info['is_installed'];
4752 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4753 }
4754 }
4755
4756 // Just before returning callbacks, sort them to prioritize free features
4757 if (!$grouped) {
4758 // Create temporary arrays for sorting
4759 $free_callbacks = array();
4760 $pro_callbacks = array();
4761
4762 // Split callbacks into free and pro
4763 foreach ($callbacks as $key => $data) {
4764 if (isset($data['pro_only']) && $data['pro_only']) {
4765 $pro_callbacks[$key] = $data;
4766 } else {
4767 $free_callbacks[$key] = $data;
4768 }
4769 }
4770
4771 // Merge with free callbacks first
4772 $callbacks = array_merge($free_callbacks, $pro_callbacks);
4773 }
4774
4775 // Return grouped structure if requested
4776 if ($grouped) {
4777 $grouped_callbacks = array();
4778 foreach ($callbacks as $key => $data) {
4779 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4780
4781 // Ensure we carry forward all the new fields in grouped mode
4782 $callback_data = array(
4783 'label' => $data['label'],
4784 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4785 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4786 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4787 'addon' => isset($data['addon']) ? $data['addon'] : false,
4788 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4789 'installed' => isset($data['installed']) ? $data['installed'] : true
4790 );
4791
4792 $grouped_callbacks[$group_label][$key] = $callback_data;
4793 }
4794
4795 // Sort within each group to prioritize free features
4796 foreach ($grouped_callbacks as $group => $items) {
4797 $free_items = array();
4798 $pro_items = array();
4799
4800 foreach ($items as $key => $data) {
4801 if (isset($data['pro_only']) && $data['pro_only']) {
4802 $pro_items[$key] = $data;
4803 } else {
4804 $free_items[$key] = $data;
4805 }
4806 }
4807
4808 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4809 }
4810
4811 return $grouped_callbacks;
4812 }
4813
4814 return $callbacks;
4815 }
4816
4817
4818 private function mxchat_average_vectors($vectors) {
4819 $vector_length = count($vectors[0]);
4820 $sum_vector = array_fill(0, $vector_length, 0);
4821
4822 foreach ($vectors as $vector) {
4823 for ($i = 0; $i < $vector_length; $i++) {
4824 $sum_vector[$i] += $vector[$i];
4825 }
4826 }
4827
4828 // Divide each component by the number of vectors to get the average
4829 $num_vectors = count($vectors);
4830 for ($i = 0; $i < $vector_length; $i++) {
4831 $sum_vector[$i] /= $num_vectors;
4832 }
4833
4834 return $sum_vector;
4835 }
4836
4837 public function mxchat_handle_delete_intent() {
4838 if ( ! current_user_can( 'manage_options' ) ) {
4839 wp_die( esc_html__('Unauthorized user', 'mxchat') );
4840 }
4841
4842 check_admin_referer('mxchat_delete_intent_nonce');
4843
4844 if (isset($_POST['intent_id'])) {
4845 global $wpdb;
4846 $table_name = $wpdb->prefix . 'mxchat_intents';
4847 $intent_id = intval($_POST['intent_id']);
4848
4849 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
4850 }
4851
4852 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4853 exit;
4854 }
4855
4856
4857 public function mxchat_page_init() {
4858 // Register settings
4859 register_setting(
4860 'mxchat_option_group',
4861 'mxchat_options',
4862 array($this, 'mxchat_sanitize')
4863 );
4864
4865 register_setting(
4866 'mxchat_option_group',
4867 'mxchat_similarity_threshold',
4868 array(
4869 'type' => 'number',
4870 'sanitize_callback' => function($value) {
4871 $value = absint($value); // Ensure it's an integer
4872 return min(max($value, 70), 85); // Enforce range
4873 },
4874 'default' => 80,
4875 )
4876 );
4877
4878 // Chatbot Settings Section
4879 add_settings_section(
4880 'mxchat_chatbot_section',
4881 esc_html__('Chatbot Settings', 'mxchat'),
4882 null,
4883 'mxchat-chatbot'
4884 );
4885
4886 // Similarity Threshold Slider
4887 add_settings_field(
4888 'similarity_threshold', // Field ID
4889 esc_html__('Similarity Threshold', 'mxchat'), // Field title
4890 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
4891 'mxchat-chatbot', // Page
4892 'mxchat_chatbot_section' // Section
4893 );
4894
4895 add_settings_field(
4896 'append_to_body',
4897 esc_html__('Auto-Display Chatbot', 'mxchat'),
4898 array($this, 'mxchat_append_to_body_callback'),
4899 'mxchat-chatbot',
4900 'mxchat_chatbot_section'
4901 );
4902
4903
4904 // Existing fields...
4905 add_settings_field(
4906 'api_key',
4907 esc_html__('OpenAI API Key', 'mxchat'),
4908 array($this, 'api_key_callback'),
4909 'mxchat-chatbot',
4910 'mxchat_chatbot_section',
4911 array(
4912 'class' => 'mxchat-setting-row',
4913 'data-provider' => 'openai'
4914 )
4915 );
4916
4917 add_settings_field(
4918 'xai_api_key',
4919 esc_html__('X.AI API Key', 'mxchat'),
4920 array($this, 'xai_api_key_callback'),
4921 'mxchat-chatbot',
4922 'mxchat_chatbot_section',
4923 array(
4924 'class' => 'mxchat-setting-row',
4925 'data-provider' => 'xai'
4926 )
4927 );
4928
4929 add_settings_field(
4930 'claude_api_key',
4931 esc_html__('Claude API Key', 'mxchat'),
4932 array($this, 'claude_api_key_callback'),
4933 'mxchat-chatbot',
4934 'mxchat_chatbot_section',
4935 array(
4936 'class' => 'mxchat-setting-row',
4937 'data-provider' => 'claude'
4938 )
4939 );
4940
4941 add_settings_field(
4942 'deepseek_api_key',
4943 esc_html__('DeepSeek API Key', 'mxchat'),
4944 array($this, 'deepseek_api_key_callback'),
4945 'mxchat-chatbot',
4946 'mxchat_chatbot_section',
4947 array(
4948 'class' => 'mxchat-setting-row',
4949 'data-provider' => 'deepseek'
4950 )
4951 );
4952
4953 add_settings_field(
4954 'gemini_api_key',
4955 esc_html__('Google Gemini API Key', 'mxchat'),
4956 array($this, 'gemini_api_key_callback'),
4957 'mxchat-chatbot',
4958 'mxchat_chatbot_section',
4959 array(
4960 'class' => 'mxchat-setting-row',
4961 'data-provider' => 'gemini'
4962 )
4963 );
4964
4965 add_settings_field(
4966 'voyage_api_key',
4967 esc_html__('Voyage AI API Key', 'mxchat'),
4968 array($this, 'voyage_api_key_callback'),
4969 'mxchat-chatbot',
4970 'mxchat_chatbot_section',
4971 array(
4972 'class' => 'mxchat-setting-row',
4973 'data-provider' => 'voyage'
4974 )
4975 );
4976
4977 add_settings_field(
4978 'model',
4979 esc_html__('Chat Model', 'mxchat'),
4980 array($this, 'mxchat_model_callback'),
4981 'mxchat-chatbot',
4982 'mxchat_chatbot_section'
4983 );
4984
4985 // Add the settings field
4986 add_settings_field(
4987 'embedding_model',
4988 esc_html__('Embedding Model', 'mxchat'),
4989 array($this, 'embedding_model_callback'),
4990 'mxchat-chatbot',
4991 'mxchat_chatbot_section'
4992 );
4993
4994 add_settings_field(
4995 'system_prompt_instructions',
4996 esc_html__('AI Instructions (Behavior)', 'mxchat'),
4997 array($this, 'system_prompt_instructions_callback'),
4998 'mxchat-chatbot',
4999 'mxchat_chatbot_section'
5000 );
5001
5002
5003 add_settings_field(
5004 'top_bar_title',
5005 esc_html__('Top Bar Title', 'mxchat'),
5006 array($this, 'mxchat_top_bar_title_callback'),
5007 'mxchat-chatbot',
5008 'mxchat_chatbot_section'
5009 );
5010
5011 add_settings_field(
5012 'ai_agent_text',
5013 esc_html__('AI Agent Text', 'mxchat'),
5014 array($this, 'mxchat_ai_agent_text_callback'),
5015 'mxchat-chatbot',
5016 'mxchat_chatbot_section'
5017 );
5018
5019 add_settings_field(
5020 'enable_email_block',
5021 esc_html__('Require Email To Chat', 'mxchat'),
5022 array($this, 'enable_email_block_callback'),
5023 'mxchat-chatbot',
5024 'mxchat_chatbot_section'
5025 );
5026
5027 add_settings_field(
5028 'email_blocker_header_content',
5029 esc_html__('Require Email Chat Content', 'mxchat'),
5030 array($this, 'email_blocker_header_content_callback'),
5031 'mxchat-chatbot',
5032 'mxchat_chatbot_section'
5033 );
5034
5035 add_settings_field(
5036 'email_blocker_button_text',
5037 esc_html__('Require Email Chat Button Text', 'mxchat'),
5038 [$this, 'email_blocker_button_text_callback'],
5039 'mxchat-chatbot',
5040 'mxchat_chatbot_section'
5041 );
5042
5043 add_settings_field(
5044 'intro_message',
5045 esc_html__('Introductory Message', 'mxchat'),
5046 array($this, 'mxchat_intro_message_callback'),
5047 'mxchat-chatbot',
5048 'mxchat_chatbot_section'
5049 );
5050
5051 add_settings_field(
5052 'input_copy',
5053 esc_html__('Input Copy', 'mxchat'),
5054 array($this, 'mxchat_input_copy_callback'),
5055 'mxchat-chatbot',
5056 'mxchat_chatbot_section'
5057 );
5058
5059 add_settings_field(
5060 'rate_limit_logged_out',
5061 __('Rate Limit for Logged-out Users', 'mxchat'),
5062 array($this, 'mxchat_rate_limit_logged_out_callback'),
5063 'mxchat-chatbot',
5064 'mxchat_chatbot_section'
5065 );
5066
5067 add_settings_field(
5068 'rate_limit_roles',
5069 __('Rate Limits by User Role', 'mxchat'),
5070 array($this, 'mxchat_rate_limit_roles_callback'),
5071 'mxchat-chatbot',
5072 'mxchat_chatbot_section'
5073 );
5074
5075 add_settings_field(
5076 'rate_limit_message',
5077 esc_html__('Rate Limit Message', 'mxchat'),
5078 array($this, 'mxchat_rate_limit_message_callback'),
5079 'mxchat-chatbot',
5080 'mxchat_chatbot_section'
5081 );
5082
5083 add_settings_field(
5084 'pre_chat_message',
5085 esc_html__('Chat Teaser Pop-up', 'mxchat'),
5086 array($this, 'mxchat_pre_chat_message_callback'),
5087 'mxchat-chatbot',
5088 'mxchat_chatbot_section'
5089 );
5090
5091 add_settings_field(
5092 'privacy_toggle',
5093 esc_html__('Toggle Privacy Notice', 'mxchat'),
5094 array($this, 'mxchat_privacy_toggle_callback'),
5095 'mxchat-chatbot',
5096 'mxchat_chatbot_section'
5097 );
5098
5099 add_settings_field(
5100 'complianz_toggle',
5101 esc_html__('Enable Complianz', 'mxchat'),
5102 array($this, 'mxchat_complianz_toggle_callback'),
5103 'mxchat-chatbot',
5104 'mxchat_chatbot_section'
5105 );
5106
5107 add_settings_field(
5108 'link_target_toggle',
5109 esc_html__('Open Links in a New Tab', 'mxchat'),
5110 array($this, 'mxchat_link_target_toggle_callback'),
5111 'mxchat-chatbot',
5112 'mxchat_chatbot_section'
5113 );
5114
5115 add_settings_field(
5116 'chat_persistence_toggle',
5117 esc_html__('Enable Chat Persistence', 'mxchat'),
5118 array($this, 'mxchat_chat_persistence_toggle_callback'),
5119 'mxchat-chatbot',
5120 'mxchat_chatbot_section'
5121 );
5122
5123 add_settings_field(
5124 'popular_question_1',
5125 esc_html__('Quick Question 1', 'mxchat'),
5126 array($this, 'mxchat_popular_question_1_callback'),
5127 'mxchat-chatbot',
5128 'mxchat_chatbot_section'
5129 );
5130
5131 add_settings_field(
5132 'popular_question_2',
5133 esc_html__('Quick Question 2', 'mxchat'),
5134 array($this, 'mxchat_popular_question_2_callback'),
5135 'mxchat-chatbot',
5136 'mxchat_chatbot_section'
5137 );
5138
5139 add_settings_field(
5140 'popular_question_3',
5141 esc_html__('Quick Question 3', 'mxchat'),
5142 array($this, 'mxchat_popular_question_3_callback'),
5143 'mxchat-chatbot',
5144 'mxchat_chatbot_section'
5145 );
5146
5147 add_settings_field(
5148 'additional_popular_questions',
5149 esc_html__('Additional Quick Questions', 'mxchat'),
5150 array($this, 'mxchat_additional_popular_questions_callback'),
5151 'mxchat-chatbot',
5152 'mxchat_chatbot_section'
5153 );
5154
5155
5156 // Loops Settings Section
5157 add_settings_section(
5158 'mxchat_loops_section',
5159 esc_html__('Loops Settings', 'mxchat'),
5160 null,
5161 'mxchat-embed'
5162 );
5163
5164 // Loops Settings Fields
5165 add_settings_field(
5166 'loops_api_key',
5167 esc_html__('Loops API Key', 'mxchat'),
5168 array($this, 'mxchat_loops_api_key_callback'),
5169 'mxchat-embed',
5170 'mxchat_loops_section'
5171 );
5172
5173 add_settings_field(
5174 'loops_mailing_list',
5175 esc_html__('Loops Mailing List', 'mxchat'),
5176 array($this, 'mxchat_loops_mailing_list_callback'),
5177 'mxchat-embed',
5178 'mxchat_loops_section'
5179 );
5180
5181 add_settings_field(
5182 'triggered_phrase_response',
5183 esc_html__('Triggered Phrase Response', 'mxchat'),
5184 array($this, 'mxchat_triggered_phrase_response_callback'),
5185 'mxchat-embed',
5186 'mxchat_loops_section'
5187 );
5188
5189 add_settings_field(
5190 'email_capture_response',
5191 esc_html__('Email Capture Response', 'mxchat'),
5192 array($this, 'mxchat_email_capture_response_callback'),
5193 'mxchat-embed',
5194 'mxchat_loops_section'
5195 );
5196
5197 // Brave Search Settings Fields
5198 add_settings_section(
5199 'mxchat_brave_section',
5200 __('Brave Search Settings', 'mxchat'),
5201 array($this, 'mxchat_brave_section_callback'),
5202 'mxchat-embed'
5203 );
5204
5205 add_settings_field(
5206 'brave_api_key',
5207 __('Brave API Key', 'mxchat'),
5208 array($this, 'mxchat_brave_api_key_callback'),
5209 'mxchat-embed',
5210 'mxchat_brave_section'
5211 );
5212
5213 add_settings_field(
5214 'brave_image_count',
5215 __('Number of Images to Return', 'mxchat'),
5216 array($this, 'mxchat_brave_image_count_callback'),
5217 'mxchat-embed',
5218 'mxchat_brave_section'
5219 );
5220
5221 add_settings_field(
5222 'brave_safe_search',
5223 __('Safe Search', 'mxchat'),
5224 array($this, 'mxchat_brave_safe_search_callback'),
5225 'mxchat-embed',
5226 'mxchat_brave_section'
5227 );
5228
5229 add_settings_field(
5230 'brave_news_count',
5231 __('Number of News Articles', 'mxchat'),
5232 array($this, 'mxchat_brave_news_count_callback'),
5233 'mxchat-embed',
5234 'mxchat_brave_section'
5235 );
5236
5237 add_settings_field(
5238 'brave_country',
5239 __('Country', 'mxchat'),
5240 array($this, 'mxchat_brave_country_callback'),
5241 'mxchat-embed',
5242 'mxchat_brave_section'
5243 );
5244
5245 add_settings_field(
5246 'brave_language',
5247 __('Language', 'mxchat'),
5248 array($this, 'mxchat_brave_language_callback'),
5249 'mxchat-embed',
5250 'mxchat_brave_section'
5251 );
5252
5253 // Chat with PDF Intent Settings Fields
5254 add_settings_section(
5255 'mxchat_pdf_intent_section',
5256 __('Toolbar Settings & Intents', 'mxchat'),
5257 array($this, 'mxchat_pdf_intent_section_callback'),
5258 'mxchat-embed'
5259 );
5260
5261 add_settings_field(
5262 'chat_toolbar_toggle',
5263 __('Show Chat Toolbar', 'mxchat'),
5264 array($this, 'mxchat_chat_toolbar_toggle_callback'),
5265 'mxchat-embed',
5266 'mxchat_pdf_intent_section'
5267 );
5268
5269 // PDF Upload Button Toggle
5270 add_settings_field(
5271 'show_pdf_upload_button',
5272 __('Show PDF Upload Button', 'mxchat'),
5273 array($this, 'mxchat_show_pdf_upload_button_callback'),
5274 'mxchat-embed',
5275 'mxchat_pdf_intent_section'
5276 );
5277
5278 // Word Upload Button Toggle
5279 add_settings_field(
5280 'show_word_upload_button',
5281 __('Show Word Upload Button', 'mxchat'),
5282 array($this, 'mxchat_show_word_upload_button_callback'),
5283 'mxchat-embed',
5284 'mxchat_pdf_intent_section'
5285 );
5286
5287 add_settings_field(
5288 'pdf_intent_trigger_text',
5289 __('Intent Trigger Text', 'mxchat'),
5290 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
5291 'mxchat-embed',
5292 'mxchat_pdf_intent_section'
5293 );
5294
5295 add_settings_field(
5296 'pdf_intent_success_text',
5297 __('Success Text', 'mxchat'),
5298 array($this, 'mxchat_pdf_intent_success_text_callback'),
5299 'mxchat-embed',
5300 'mxchat_pdf_intent_section'
5301 );
5302
5303 add_settings_field(
5304 'pdf_intent_error_text',
5305 __('Error Text', 'mxchat'),
5306 array($this, 'mxchat_pdf_intent_error_text_callback'),
5307 'mxchat-embed',
5308 'mxchat_pdf_intent_section'
5309 );
5310
5311 // Add PDF Maximum Pages Field
5312 add_settings_field(
5313 'pdf_max_pages',
5314 __('Maximum Document Pages', 'mxchat'),
5315 array($this, 'mxchat_pdf_max_pages_callback'),
5316 'mxchat-embed',
5317 'mxchat_pdf_intent_section'
5318 );
5319
5320 // Live Agent Settings Fields
5321 add_settings_section(
5322 'mxchat_live_agent_section',
5323 __('Live Agent Settings', 'mxchat'),
5324 array($this, 'mxchat_live_agent_section_callback'),
5325 'mxchat-embed'
5326 );
5327
5328 // Live Agent Status Fields (add at top of live agent settings)
5329 add_settings_field(
5330 'live_agent_status',
5331 __('Live Agent Status', 'mxchat'),
5332 array($this, 'mxchat_live_agent_status_callback'),
5333 'mxchat-embed',
5334 'mxchat_live_agent_section'
5335 );
5336
5337 add_settings_field(
5338 'live_agent_notification_message',
5339 __('Notification Message', 'mxchat'),
5340 array($this, 'mxchat_live_agent_notification_message_callback'),
5341 'mxchat-embed',
5342 'mxchat_live_agent_section'
5343 );
5344
5345 add_settings_field(
5346 'live_agent_away_message',
5347 __('Away Message', 'mxchat'),
5348 array($this, 'mxchat_live_agent_away_message_callback'),
5349 'mxchat-embed',
5350 'mxchat_live_agent_section'
5351 );
5352
5353 add_settings_field(
5354 'live_agent_webhook_url',
5355 __('Slack Webhook URL', 'mxchat'),
5356 array($this, 'mxchat_live_agent_webhook_url_callback'),
5357 'mxchat-embed',
5358 'mxchat_live_agent_section'
5359 );
5360
5361 add_settings_field(
5362 'live_agent_secret_key',
5363 __('Slack Secret Key', 'mxchat'),
5364 array($this, 'mxchat_live_agent_secret_key_callback'),
5365 'mxchat-embed',
5366 'mxchat_live_agent_section'
5367 );
5368
5369 // Live Agent Integration Fields
5370 add_settings_field(
5371 'live_agent_bot_token',
5372 __('Slack Bot OAuth Token', 'mxchat'),
5373 array($this, 'mxchat_live_agent_bot_token_callback'),
5374 'mxchat-embed',
5375 'mxchat_live_agent_section'
5376 );
5377
5378
5379
5380
5381 // General Settings Section
5382 add_settings_section(
5383 'mxchat_general_section',
5384 esc_html__('YouTube Tutorials', 'mxchat'),
5385 null,
5386 'mxchat-general'
5387 );
5388 }
5389
5390 public function mxchat_prompts_page_init() {
5391 // Register all settings as a single array
5392 register_setting(
5393 'mxchat_prompts_options',
5394 'mxchat_prompts_options',
5395 array(
5396 'type' => 'array',
5397 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
5398 'default' => array(
5399 'mxchat_auto_sync_posts' => 0,
5400 'mxchat_auto_sync_pages' => 0,
5401 'mxchat_use_pinecone' => 0,
5402 'mxchat_pinecone_api_key' => '',
5403 'mxchat_pinecone_environment' => '',
5404 'mxchat_pinecone_index' => '',
5405 'mxchat_pinecone_host' => '', // Add this line
5406 ),
5407 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
5408 )
5409 );
5410
5411 // Add settings saved message
5412 add_action('admin_notices', array($this, 'sync_settings_notice'));
5413 }
5414
5415 /**
5416 * Sanitize all prompts options
5417 *
5418 * @param array $input The unsanitized options array
5419 * @return array The sanitized options array
5420 */
5421 public function sanitize_prompts_options($input) {
5422 // Log the incoming input.
5423 //error_log('Sanitizing inputs: ' . print_r($input, true));
5424
5425 $sanitized = array();
5426
5427 // Boolean options
5428 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
5429 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
5430 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
5431
5432 // API Key: if less than 32 characters, flag as invalid.
5433 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
5434 if (!empty($api_key) && strlen($api_key) < 32) {
5435 add_settings_error(
5436 'mxchat_prompts_options',
5437 'invalid_api_key',
5438 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
5439 );
5440 $existing_options = get_option('mxchat_prompts_options', array());
5441 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
5442 } else {
5443 $sanitized['mxchat_pinecone_api_key'] = $api_key;
5444 }
5445
5446 // Environment and Index Name
5447 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
5448 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
5449
5450 // Host: Remove protocol and validate format.
5451 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
5452 $host = preg_replace('#^https?://#', '', $host);
5453 //error_log('Host after removing protocol: ' . $host);
5454 if (!empty($host)) {
5455 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
5456 add_settings_error(
5457 'mxchat_prompts_options',
5458 'invalid_host',
5459 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
5460 );
5461 $existing_options = get_option('mxchat_prompts_options', array());
5462 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
5463 } else {
5464 $sanitized['mxchat_pinecone_host'] = $host;
5465 }
5466 } else {
5467 $sanitized['mxchat_pinecone_host'] = '';
5468 }
5469
5470 //error_log('Final sanitized array: ' . print_r($sanitized, true));
5471
5472 return $sanitized;
5473 }
5474
5475
5476 public function sync_settings_notice() {
5477 // Only show notice on our plugin page
5478 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
5479 return;
5480 }
5481
5482 // Check if settings were updated
5483 if (isset($_GET['settings-updated'])) {
5484
5485 ?>
5486 <div class="notice notice-success is-dismissible">
5487 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
5488 </div>
5489 <?php
5490
5491 }
5492 }
5493 // Add this sanitization function to your class
5494 public function sanitize_sync_setting($input) {
5495 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
5496 }
5497
5498
5499
5500 public function mxchat_handle_activate_license() {
5501 // Check nonce
5502 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
5503 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
5504 return;
5505 }
5506
5507 // Verify user capabilities
5508 if (!current_user_can('manage_options')) {
5509 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
5510 return;
5511 }
5512
5513 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
5514 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
5515
5516 if (empty($license_key) || empty($customer_email)) {
5517 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
5518 return;
5519 }
5520
5521 $product_id = 'MxChatPRO';
5522 $response = wp_remote_get(
5523 add_query_arg(
5524 array(
5525 'wc-api' => 'software-api',
5526 'request' => 'activation',
5527 'email' => $customer_email,
5528 'license_key' => $license_key,
5529 'product_id' => $product_id
5530 ),
5531 'http://mxchat.ai/'
5532 )
5533 );
5534
5535 if (is_wp_error($response)) {
5536 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
5537 return;
5538 }
5539
5540 $body = wp_remote_retrieve_body($response);
5541 $data = json_decode($body);
5542
5543 if ($data && isset($data->activated) && $data->activated) {
5544 update_option('mxchat_license_status', 'active');
5545 update_option('mxchat_pro_email', $customer_email);
5546 update_option('mxchat_activation_key', $license_key);
5547 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
5548 } else {
5549 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
5550 update_option('mxchat_license_status', 'inactive');
5551 update_option('mxchat_license_error', $error_message);
5552 wp_send_json_error($error_message);
5553 }
5554 }
5555
5556
5557 public function mxchat_rate_limit_logged_out_callback() {
5558 // Load the entire 'mxchat_options' array
5559 $all_options = get_option('mxchat_options', []);
5560
5561 // Retrieve the saved rate limit or use the default value
5562 $default_rate_limit = '10';
5563 $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
5564
5565 // Define available rate limits
5566 $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5567
5568 // Output the dropdown
5569 echo '<div class="pro-feature-wrapper active">';
5570 echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
5571 foreach ($rate_limits as $limit) {
5572 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5573 }
5574 echo '</select>';
5575 echo '<p class="description">' . esc_html__('Set the maximum number of messages a logged-out user can send within a 24-hour period.', 'mxchat') . '</p>';
5576 echo '</div>';
5577 }
5578
5579 // Add this new callback function
5580 public function mxchat_rate_limit_roles_callback() {
5581 $all_options = get_option('mxchat_options', []);
5582 $roles = wp_roles()->get_names();
5583 $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5584
5585 echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
5586
5587 foreach ($roles as $role_id => $role_name) {
5588 $default_rate_limit = '100';
5589 $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
5590 ? $all_options['role_rate_limits'][$role_id]
5591 : $default_rate_limit;
5592
5593 echo '<div style="margin-bottom: 10px;">';
5594 echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
5595 echo '<select
5596 id="role_rate_limits_' . esc_attr($role_id) . '"
5597 name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
5598 class="mxchat-autosave-field">';
5599 foreach ($rate_limits as $limit) {
5600 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5601 }
5602 echo '</select>';
5603 echo '</div>';
5604 }
5605
5606 echo '<p class="description">' . esc_html__('Set the maximum number of messages users in each role can send within a 24-hour period.', 'mxchat') . '</p>';
5607 echo '</div>';
5608 }
5609
5610 public function mxchat_rate_limit_message_callback() {
5611 // Load the entire 'mxchat_options' array
5612 $all_options = get_option('mxchat_options', []);
5613
5614 // Retrieve the saved message or use the default value
5615 $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
5616 $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
5617
5618 // Output the textarea
5619 echo '<div class="pro-feature-wrapper active">';
5620 printf(
5621 '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
5622 esc_textarea($rate_limit_message)
5623 );
5624 echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
5625 echo '</div>';
5626 }
5627
5628
5629 private function mxchat_add_option_field($id, $title, $callback = '') {
5630 add_settings_field(
5631 $id,
5632 __($title, 'mxchat'),
5633 $callback ? array($this, $callback) : array($this, $id . '_callback'),
5634 'mxchat-max',
5635 'mxchat_setting_section_id',
5636 $id === 'model' ? ['label_for' => 'model'] : []
5637 );
5638 }
5639
5640 // OpenAI API Key
5641 public function api_key_callback() {
5642 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5643
5644 echo '<div class="api-key-wrapper" data-provider="openai">';
5645 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5646 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5647 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5648 echo '</div>';
5649 }
5650
5651 // X.AI API Key
5652 public function xai_api_key_callback() {
5653 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5654
5655 echo '<div class="api-key-wrapper" data-provider="xai">';
5656 echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
5657 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5658 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5659 echo '</div>';
5660 }
5661 // Claude API Key
5662 public function claude_api_key_callback() {
5663 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5664
5665 echo '<div class="api-key-wrapper" data-provider="claude">';
5666 echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
5667 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5668 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5669 echo '</div>';
5670 }
5671
5672 // DeepSeek API Key
5673 public function deepseek_api_key_callback() {
5674 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5675
5676 echo '<div class="api-key-wrapper" data-provider="deepseek">';
5677 echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5678 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5679 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5680 echo '</div>';
5681 }
5682
5683 // Gemini API Key
5684 public function gemini_api_key_callback() {
5685 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5686
5687 echo '<div class="api-key-wrapper" data-provider="gemini">';
5688 echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
5689 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5690 echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5691 echo '</div>';
5692 }
5693
5694 // Voyage API Key
5695 public function voyage_api_key_callback() {
5696 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5697
5698 echo '<div class="api-key-wrapper" data-provider="voyage">';
5699 echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5700 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5701 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '</p>';
5702 echo '</div>';
5703 }
5704
5705 public function mxchat_loops_api_key_callback() {
5706 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5707
5708 // Hidden fields to "trap" autofill
5709 echo '<input type="text" style="display:none" autocomplete="username" />';
5710 echo '<input type="password" style="display:none" autocomplete="current-password" />';
5711
5712 echo '<div class="api-key-wrapper" data-provider="loops">';
5713 echo sprintf(
5714 '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
5715 $loops_api_key
5716 );
5717 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5718 echo '</div>';
5719 echo '<p class="description">' . esc_html__('Enter your Loops API Key here. Once entered, refreshed page to load list (See FAQ for details)', 'mxchat') . '</p>';
5720 }
5721 public function mxchat_loops_mailing_list_callback() {
5722 // Add error handling and type checking
5723 $loops_api_key = '';
5724 $selected_list = '';
5725
5726 // Safely get the API key
5727 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
5728 $loops_api_key = $this->options['loops_api_key'];
5729 }
5730
5731 // Safely get the selected list
5732 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
5733 $selected_list = $this->options['loops_mailing_list'];
5734 }
5735
5736 if (!empty($loops_api_key)) {
5737 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
5738 if (is_array($lists) && !empty($lists)) {
5739 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
5740 foreach ($lists as $list) {
5741 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
5742 echo sprintf(
5743 '<option value="%s" %s>%s</option>',
5744 esc_attr($list['id']),
5745 selected($selected_list, $list['id'], false),
5746 esc_html($list['name'])
5747 );
5748 }
5749 }
5750 echo '</select>';
5751 } else {
5752 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
5753 }
5754 } else {
5755 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
5756 }
5757 }
5758
5759 public function mxchat_triggered_phrase_response_callback() {
5760 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
5761 $triggered_response = isset($this->options['triggered_phrase_response'])
5762 ? $this->options['triggered_phrase_response']
5763 : $default_response;
5764
5765 echo sprintf(
5766 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
5767 esc_textarea($triggered_response)
5768 );
5769 echo '<p class="description">' . esc_html__('Enter the chatbot response when a trigger keyword is detected, prompting the user to share their email.', 'mxchat') . '</p>';
5770 }
5771
5772 public function mxchat_email_capture_response_callback() {
5773 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
5774 $email_capture_response = isset($this->options['email_capture_response'])
5775 ? $this->options['email_capture_response']
5776 : $default_response;
5777
5778 echo sprintf(
5779 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
5780 esc_textarea($email_capture_response)
5781 );
5782 echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
5783 }
5784
5785 public function mxchat_pre_chat_message_callback() {
5786 // Load the entire 'mxchat_options' array
5787 $all_options = get_option('mxchat_options', []);
5788
5789 // Retrieve the saved message or use the default value
5790 $default_message = __('Hey there! Ask me anything!', 'mxchat');
5791 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
5792
5793 // Output the textarea
5794 printf(
5795 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
5796 esc_textarea($pre_chat_message)
5797 );
5798 echo '<p class="description">' . esc_html__('Set the message displayed to users before they start a chat. Use this to provide a friendly greeting or instructions.', 'mxchat') . '</p>';
5799 }
5800
5801
5802
5803 // Callback for AI Instructions textarea
5804 public function system_prompt_instructions_callback() {
5805 // Retrieve the current value of the system prompt instructions
5806 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
5807
5808 // Render the textarea field
5809 printf(
5810 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
5811 $instructions
5812 );
5813
5814 // Provide a helpful description
5815 echo '<p class="description">' . esc_html__('Provide system-level instructions for the AI to guide its behavior. Be clear and concise for better results.', 'mxchat') . '</p>';
5816 }
5817
5818
5819 public function mxchat_model_callback() {
5820 // Define available models grouped by provider
5821 $models = array(
5822 esc_html__('Google Gemini Models', 'mxchat') => array(
5823 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5824 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5825 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5826 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
5827 ),
5828 esc_html__('X.AI Models', 'mxchat') => array(
5829 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5830 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5831 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5832 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5833 'grok-2' => esc_html__('Grok 2', 'mxchat')
5834 ),
5835 esc_html__('DeepSeek Models', 'mxchat') => array(
5836 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
5837 ),
5838 esc_html__('Claude Models', 'mxchat') => array(
5839 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (Most Intelligent)', 'mxchat'),
5840 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Intelligent)', 'mxchat'),
5841 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'),
5842 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'),
5843 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat')
5844 ),
5845 esc_html__('OpenAI Models', 'mxchat') => array(
5846 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'),
5847 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
5848 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
5849 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
5850 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
5851 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
5852 )
5853 );
5854
5855 // Retrieve the currently selected model from saved options
5856 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
5857
5858 // Begin the select dropdown
5859 echo '<select id="model" name="model">';
5860
5861 // Iterate over groups of models
5862 foreach ($models as $group_label => $group_models) {
5863 echo '<optgroup label="' . esc_attr($group_label) . '">';
5864
5865 foreach ($group_models as $model_value => $model_label) {
5866 // All models enabled - no disabled attribute or Pro Only label
5867 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5868 }
5869
5870 echo '</optgroup>';
5871 }
5872
5873 // Close the select dropdown
5874 echo '</select>';
5875
5876 // Updated description to remove mention of Pro-only models
5877 echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
5878 }
5879
5880
5881 // Callback function for embedding model selection
5882 public function embedding_model_callback() {
5883 $models = array(
5884 esc_html__('OpenAI Embeddings', 'mxchat') => array(
5885 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5886 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5887 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5888 ),
5889 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5890 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5891 )
5892 );
5893
5894 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5895
5896 echo '<select id="embedding_model" name="embedding_model">';
5897 foreach ($models as $group_label => $group_models) {
5898 echo '<optgroup label="' . esc_attr($group_label) . '">';
5899 foreach ($group_models as $model_value => $model_label) {
5900 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5901 }
5902 echo '</optgroup>';
5903 }
5904 echo '</select>';
5905 echo '<p class="description"><span class="red-warning">IMPORTANT:</span> Select the model for vector embeddings. Changing models is not recommended; if you do, you must delete all existing knowledge & intent data and reconfigure them.</p>';
5906
5907 }
5908
5909
5910 public function mxchat_top_bar_title_callback() {
5911 // Retrieve the current value of the top bar title from saved options
5912 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
5913
5914 // Render the input field
5915 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
5916
5917 // Add a description
5918 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
5919 }
5920 public function mxchat_ai_agent_text_callback() {
5921 // Retrieve the current value of the AI agent text from saved options
5922 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5923 // Render the input field
5924 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5925 // Add a description
5926 echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
5927 }
5928 public function enable_email_block_callback() {
5929 // Load full plugin options array
5930 $all_options = get_option('mxchat_options', []);
5931
5932 // Get the value, default to 'off'
5933 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
5934
5935 // Check if it's 'on'
5936 $checked = ($enable_email_block === 'on') ? 'checked' : '';
5937
5938 echo '<label class="toggle-switch">';
5939 echo sprintf(
5940 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
5941 esc_attr($checked)
5942 );
5943 echo '<span class="slider"></span>';
5944 echo '</label>';
5945 echo '<p class="description">' . esc_html__('Their email will appear at the top of the transcript. Email form will show for users who are not logged in or have not provided an email within 24h.', 'mxchat') . '</p>';
5946 }
5947
5948
5949 public function email_blocker_header_content_callback() {
5950 // Load the entire 'mxchat_options' array
5951 $all_options = get_option('mxchat_options', []);
5952
5953 // Retrieve the saved content or default to empty
5954 $content = isset($all_options['email_blocker_header_content'])
5955 ? $all_options['email_blocker_header_content']
5956 : '';
5957
5958 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
5959 echo '<textarea
5960 id="email_blocker_header_content"
5961 name="email_blocker_header_content"
5962 rows="5"
5963 cols="70"
5964 data-setting="email_blocker_header_content"
5965 >' . esc_textarea($content) . '</textarea>';
5966
5967 echo '<p class="description">';
5968 echo esc_html__('You may enter HTML here, such as &lt;h2&gt;Welcome&lt;/h2&gt; or &lt;p&gt;Let\'s get started&lt;/p&gt;.', 'mxchat');
5969 echo '</p>';
5970 }
5971
5972 public function email_blocker_button_text_callback() {
5973 // Load the entire 'mxchat_options' array
5974 $all_options = get_option('mxchat_options', []);
5975
5976 // Retrieve the saved button text or default to empty
5977 $button_text = isset($all_options['email_blocker_button_text'])
5978 ? $all_options['email_blocker_button_text']
5979 : '';
5980
5981 // Use esc_attr to safely render the existing text
5982 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
5983
5984 echo '<p class="description">';
5985 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
5986 echo '</p>';
5987 }
5988
5989
5990
5991 public function mxchat_intro_message_callback() {
5992 // Load the entire 'mxchat_options' array
5993 $all_options = get_option('mxchat_options', []);
5994 // Retrieve the saved intro message or use the default
5995 $default_message = __('Hello! How can I assist you today?', 'mxchat');
5996 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
5997 // Output the textarea with the saved value without escaping HTML
5998 ?>
5999 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6000 <p class="description">
6001 <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
6002 </p>
6003 <?php
6004 }
6005
6006 public function mxchat_input_copy_callback() {
6007 // Load the entire 'mxchat_options' array
6008 $all_options = get_option('mxchat_options', []);
6009
6010 // Retrieve the saved input copy or use the default value
6011 $default_copy = __('How can I assist?', 'mxchat');
6012 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6013
6014 // Output the input field with the saved value
6015 printf(
6016 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6017 esc_attr($input_copy),
6018 esc_attr__('How can I assist?', 'mxchat')
6019 );
6020
6021 // Output the description
6022 echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
6023 }
6024
6025
6026
6027 public function mxchat_user_message_font_color_callback() {
6028 $disabled = $this->is_activated ? '' : 'disabled';
6029 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6030
6031 echo '<div class="' . esc_attr($class) . '">';
6032 echo sprintf(
6033 '<input type="text"
6034 id="user_message_font_color"
6035 name="user_message_font_color"
6036 value="%s"
6037 class="my-color-field"
6038 data-default-color="#ffffff"
6039 %s />',
6040 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6041 esc_attr($disabled)
6042 );
6043
6044 if (!$this->is_activated) {
6045 echo '<div class="pro-feature-overlay">';
6046 echo '<a href="https://mxchat.ai/" target="_blank">';
6047 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6048 echo '</a>';
6049 echo '</div>';
6050 }
6051 echo '</div>';
6052 }
6053
6054 public function mxchat_bot_message_bg_color_callback() {
6055 $disabled = $this->is_activated ? '' : 'disabled';
6056 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6057
6058 echo '<div class="' . esc_attr($class) . '">';
6059 echo sprintf(
6060 '<input type="text"
6061 id="bot_message_bg_color"
6062 name="bot_message_bg_color"
6063 value="%s"
6064 class="my-color-field"
6065 data-default-color="#e1e1e1"
6066 %s />',
6067 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6068 esc_attr($disabled)
6069 );
6070
6071 if (!$this->is_activated) {
6072 echo '<div class="pro-feature-overlay">';
6073 echo '<a href="https://mxchat.ai/" target="_blank">';
6074 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6075 echo '</a>';
6076 echo '</div>';
6077 }
6078 echo '</div>';
6079 }
6080
6081 public function mxchat_bot_message_font_color_callback() {
6082 $disabled = $this->is_activated ? '' : 'disabled';
6083 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6084
6085 echo '<div class="' . esc_attr($class) . '">';
6086 echo sprintf(
6087 '<input type="text"
6088 id="bot_message_font_color"
6089 name="bot_message_font_color"
6090 value="%s"
6091 class="my-color-field"
6092 data-default-color="#333333"
6093 %s />',
6094 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6095 esc_attr($disabled)
6096 );
6097
6098 if (!$this->is_activated) {
6099 echo '<div class="pro-feature-overlay">';
6100 echo '<a href="https://mxchat.ai/" target="_blank">';
6101 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6102 echo '</a>';
6103 echo '</div>';
6104 }
6105 echo '</div>';
6106 }
6107
6108 public function mxchat_live_agent_message_bg_color_callback() {
6109 $disabled = $this->is_activated ? '' : 'disabled';
6110 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6111
6112 echo '<div class="' . esc_attr($class) . '">';
6113 echo sprintf(
6114 '<input type="text"
6115 id="live_agent_message_bg_color"
6116 name="live_agent_message_bg_color"
6117 value="%s"
6118 class="my-color-field"
6119 data-default-color="#ffffff"
6120 %s />',
6121 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
6122 esc_attr($disabled)
6123 );
6124
6125 if (!$this->is_activated) {
6126 echo '<div class="pro-feature-overlay">';
6127 echo '<a href="https://mxchat.ai/" target="_blank">';
6128 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6129 echo '</a>';
6130 echo '</div>';
6131 }
6132 echo '</div>';
6133 }
6134
6135 public function mxchat_live_agent_message_font_color_callback() {
6136 $disabled = $this->is_activated ? '' : 'disabled';
6137 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6138
6139 echo '<div class="' . esc_attr($class) . '">';
6140 echo sprintf(
6141 '<input type="text"
6142 id="live_agent_message_font_color"
6143 name="live_agent_message_font_color"
6144 value="%s"
6145 class="my-color-field"
6146 data-default-color="#333333"
6147 %s />',
6148 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
6149 esc_attr($disabled)
6150 );
6151
6152 if (!$this->is_activated) {
6153 echo '<div class="pro-feature-overlay">';
6154 echo '<a href="https://mxchat.ai/" target="_blank">';
6155 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6156 echo '</a>';
6157 echo '</div>';
6158 }
6159 echo '</div>';
6160 }
6161
6162 public function mxchat_mode_indicator_bg_color_callback() {
6163 $disabled = $this->is_activated ? '' : 'disabled';
6164 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6165
6166 echo '<div class="' . esc_attr($class) . '">';
6167 echo sprintf(
6168 '<input type="text"
6169 id="mode_indicator_bg_color"
6170 name="mode_indicator_bg_color"
6171 value="%s"
6172 class="my-color-field"
6173 data-default-color="#767676"
6174 %s />',
6175 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
6176 esc_attr($disabled)
6177 );
6178
6179 if (!$this->is_activated) {
6180 echo '<div class="pro-feature-overlay">';
6181 echo '<a href="https://mxchat.ai/" target="_blank">';
6182 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6183 echo '</a>';
6184 echo '</div>';
6185 }
6186 echo '</div>';
6187 }
6188
6189 public function mxchat_mode_indicator_font_color_callback() {
6190 $disabled = $this->is_activated ? '' : 'disabled';
6191 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6192
6193 echo '<div class="' . esc_attr($class) . '">';
6194 echo sprintf(
6195 '<input type="text"
6196 id="mode_indicator_font_color"
6197 name="mode_indicator_font_color"
6198 value="%s"
6199 class="my-color-field"
6200 data-default-color="#ffffff"
6201 %s />',
6202 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
6203 esc_attr($disabled)
6204 );
6205
6206 if (!$this->is_activated) {
6207 echo '<div class="pro-feature-overlay">';
6208 echo '<a href="https://mxchat.ai/" target="_blank">';
6209 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6210 echo '</a>';
6211 echo '</div>';
6212 }
6213 echo '</div>';
6214 }
6215
6216 public function mxchat_toolbar_icon_color_callback() {
6217 $disabled = $this->is_activated ? '' : 'disabled';
6218 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6219
6220 echo '<div class="' . esc_attr($class) . '">';
6221 echo sprintf(
6222 '<input type="text"
6223 id="toolbar_icon_color"
6224 name="toolbar_icon_color"
6225 value="%s"
6226 class="my-color-field"
6227 data-default-color="#212121"
6228 %s />',
6229 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
6230 esc_attr($disabled)
6231 );
6232
6233 if (!$this->is_activated) {
6234 echo '<div class="pro-feature-overlay">';
6235 echo '<a href="https://mxchat.ai/" target="_blank">';
6236 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6237 echo '</a>';
6238 echo '</div>';
6239 }
6240 echo '</div>';
6241 }
6242
6243 public function mxchat_top_bar_bg_color_callback() {
6244 $disabled = $this->is_activated ? '' : 'disabled';
6245 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6246
6247 echo '<div class="' . esc_attr($class) . '">';
6248 echo sprintf(
6249 '<input type="text"
6250 id="top_bar_bg_color"
6251 name="top_bar_bg_color"
6252 value="%s"
6253 class="my-color-field"
6254 data-default-color="#00b294"
6255 %s />',
6256 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
6257 esc_attr($disabled)
6258 );
6259
6260 if (!$this->is_activated) {
6261 echo '<div class="pro-feature-overlay">';
6262 echo '<a href="https://mxchat.ai/" target="_blank">';
6263 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6264 echo '</a>';
6265 echo '</div>';
6266 }
6267 echo '</div>';
6268 }
6269
6270 public function mxchat_send_button_font_color_callback() {
6271 $disabled = $this->is_activated ? '' : 'disabled';
6272 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6273
6274 echo '<div class="' . esc_attr($class) . '">';
6275 echo sprintf(
6276 '<input type="text"
6277 id="send_button_font_color"
6278 name="send_button_font_color"
6279 value="%s"
6280 class="my-color-field"
6281 data-default-color="#ffffff"
6282 %s />',
6283 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
6284 esc_attr($disabled)
6285 );
6286
6287 if (!$this->is_activated) {
6288 echo '<div class="pro-feature-overlay">';
6289 echo '<a href="https://mxchat.ai/" target="_blank">';
6290 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6291 echo '</a>';
6292 echo '</div>';
6293 }
6294 echo '</div>';
6295 }
6296
6297 public function mxchat_chatbot_background_color_callback() {
6298 $disabled = $this->is_activated ? '' : 'disabled';
6299 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6300
6301 echo '<div class="' . esc_attr($class) . '">';
6302 echo sprintf(
6303 '<input type="text"
6304 id="chatbot_background_color"
6305 name="chatbot_background_color"
6306 value="%s"
6307 class="my-color-field"
6308 data-default-color="#000000"
6309 %s />',
6310 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
6311 esc_attr($disabled)
6312 );
6313
6314 if (!$this->is_activated) {
6315 echo '<div class="pro-feature-overlay">';
6316 echo '<a href="https://mxchat.ai/" target="_blank">';
6317 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6318 echo '</a>';
6319 echo '</div>';
6320 }
6321 echo '</div>';
6322 }
6323
6324 public function mxchat_icon_color_callback() {
6325 $disabled = $this->is_activated ? '' : 'disabled';
6326 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6327
6328 echo '<div class="' . esc_attr($class) . '">';
6329 echo sprintf(
6330 '<input type="text"
6331 id="icon_color"
6332 name="icon_color"
6333 value="%s"
6334 class="my-color-field"
6335 data-default-color="#ffffff"
6336 %s />',
6337 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
6338 esc_attr($disabled)
6339 );
6340
6341 if (!$this->is_activated) {
6342 echo '<div class="pro-feature-overlay">';
6343 echo '<a href="https://mxchat.ai/" target="_blank">';
6344 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6345 echo '</a>';
6346 echo '</div>';
6347 }
6348 echo '</div>';
6349 }
6350
6351 public function mxchat_custom_icon_callback() {
6352 $disabled = $this->is_activated ? '' : 'disabled';
6353 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6354 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
6355
6356 echo '<div class="' . esc_attr($class) . '">';
6357 echo sprintf(
6358 '<input type="url"
6359 id="custom_icon"
6360 name="custom_icon"
6361 value="%s"
6362 placeholder="%s"
6363 class="regular-text"
6364 %s />',
6365 $custom_icon_url,
6366 esc_attr__('Enter PNG URL', 'mxchat'),
6367 esc_attr($disabled)
6368 );
6369
6370 // Preview container for the icon
6371 if (!empty($custom_icon_url)) {
6372 echo '<div class="icon-preview" style="margin-top: 10px;">';
6373 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6374 echo '</div>';
6375 }
6376
6377 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6378
6379 if (!$this->is_activated) {
6380 echo '<div class="pro-feature-overlay">';
6381 echo '<a href="https://mxchat.ai/" target="_blank">';
6382 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6383 echo '</a>';
6384 echo '</div>';
6385 }
6386 echo '</div>';
6387 }
6388
6389 public function mxchat_title_icon_callback() {
6390 $disabled = $this->is_activated ? '' : 'disabled';
6391 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6392 // Fixed the variable reference - it was using custom_icon instead of title_icon
6393 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
6394
6395 echo '<div class="' . esc_attr($class) . '">';
6396 echo sprintf(
6397 '<input type="url"
6398 id="title_icon"
6399 name="title_icon"
6400 value="%s"
6401 placeholder="%s"
6402 class="regular-text"
6403 %s />',
6404 $title_icon_url,
6405 esc_attr__('Enter PNG URL', 'mxchat'),
6406 esc_attr($disabled)
6407 );
6408
6409 // Preview container for the icon
6410 if (!empty($title_icon_url)) {
6411 echo '<div class="icon-preview" style="margin-top: 10px;">';
6412 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6413 echo '</div>';
6414 }
6415
6416 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6417
6418 if (!$this->is_activated) {
6419 echo '<div class="pro-feature-overlay">';
6420 echo '<a href="https://mxchat.ai/" target="_blank">';
6421 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6422 echo '</a>';
6423 echo '</div>';
6424 }
6425 echo '</div>';
6426 }
6427
6428 public function mxchat_chat_input_font_color_callback() {
6429 $disabled = $this->is_activated ? '' : 'disabled';
6430 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6431
6432 echo '<div class="' . esc_attr($class) . '">';
6433 echo sprintf(
6434 '<input type="text"
6435 id="chat_input_font_color"
6436 name="chat_input_font_color"
6437 value="%s"
6438 class="my-color-field"
6439 data-default-color="#555555"
6440 %s />',
6441 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
6442 esc_attr($disabled)
6443 );
6444
6445 if (!$this->is_activated) {
6446 echo '<div class="pro-feature-overlay">';
6447 echo '<a href="https://mxchat.ai/" target="_blank">';
6448 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6449 echo '</a>';
6450 echo '</div>';
6451 }
6452 echo '</div>';
6453 }
6454
6455 public function mxchat_append_to_body_callback() {
6456 // Get value from options array, default to 'off'
6457 $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
6458 $checked = ($append_to_body === 'on') ? 'checked' : '';
6459
6460 echo '<label class="toggle-switch">';
6461 echo sprintf(
6462 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
6463 esc_attr($checked)
6464 );
6465 echo '<span class="slider"></span>';
6466 echo '</label>';
6467 echo '<p class="description">' .
6468 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
6469 '</p>';
6470
6471 }
6472
6473
6474
6475 public function mxchat_privacy_toggle_callback() {
6476 // Load from mxchat_options array
6477 $options = get_option('mxchat_options', []);
6478
6479 // Get privacy toggle value with fallback
6480 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
6481 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
6482
6483 // Get privacy text with fallback
6484 $privacy_text = isset($options['privacy_text'])
6485 ? $options['privacy_text']
6486 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
6487
6488 // Output the toggle switch
6489 echo '<label class="toggle-switch">';
6490 echo sprintf(
6491 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
6492 esc_attr($checked)
6493 );
6494 echo '<span class="slider"></span>';
6495 echo '</label>';
6496 echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
6497
6498 // Output the custom text input field
6499 echo sprintf(
6500 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
6501 esc_textarea($privacy_text)
6502 );
6503 echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
6504 }
6505
6506
6507 public function mxchat_complianz_toggle_callback() {
6508 // Load from mxchat_options array
6509 $options = get_option('mxchat_options', []);
6510
6511 // Get complianz toggle value with fallback
6512 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
6513 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
6514
6515 // Output the toggle switch
6516 echo '<label class="toggle-switch">';
6517 echo sprintf(
6518 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
6519 esc_attr($checked)
6520 );
6521 echo '<span class="slider"></span>';
6522 echo '</label>';
6523
6524 echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
6525 }
6526
6527 public function mxchat_link_target_toggle_callback() {
6528 // Load from mxchat_options array
6529 $options = get_option('mxchat_options', []);
6530
6531 // Get link target toggle value with fallback
6532 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
6533 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
6534
6535 // Output the toggle switch
6536 echo '<label class="toggle-switch">';
6537 echo sprintf(
6538 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
6539 esc_attr($checked)
6540 );
6541 echo '<span class="slider"></span>';
6542 echo '</label>';
6543 echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
6544 }
6545
6546 public function mxchat_chat_persistence_toggle_callback() {
6547 // Load from mxchat_options array
6548 $options = get_option('mxchat_options', []);
6549
6550 // Get chat persistence toggle value with fallback
6551 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
6552 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
6553
6554 // Output the toggle switch
6555 echo '<label class="toggle-switch">';
6556 echo sprintf(
6557 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
6558 esc_attr($checked)
6559 );
6560 echo '<span class="slider"></span>';
6561 echo '</label>';
6562
6563 echo '<p class="description">' . esc_html__('Enable to keep chat history when users navigate tabs or return to the site within 24 hours.', 'mxchat') . '</p>';
6564 }
6565
6566 public function mxchat_popular_question_1_callback() {
6567 // Load the full plugin options array
6568 $all_options = get_option('mxchat_options', []);
6569
6570 // Retrieve the specific option for popular_question_1
6571 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
6572
6573 // Render the input field
6574 printf(
6575 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
6576 esc_attr($popular_question_1),
6577 esc_attr__('Enter Quick Question 1', 'mxchat')
6578 );
6579
6580 // Add a description for the field
6581 echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
6582 }
6583
6584
6585 public function mxchat_popular_question_2_callback() {
6586 // Load the full plugin options array
6587 $all_options = get_option('mxchat_options', []);
6588
6589 // Retrieve the specific option for popular_question_2
6590 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
6591
6592 // Render the input field
6593 printf(
6594 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
6595 esc_attr($popular_question_2),
6596 esc_attr__('Enter Quick Question 2', 'mxchat')
6597 );
6598
6599 // Add a description for the field
6600 echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
6601 }
6602
6603
6604 public function mxchat_popular_question_3_callback() {
6605 // Load the full plugin options array
6606 $all_options = get_option('mxchat_options', []);
6607
6608 // Retrieve the specific option for popular_question_3
6609 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
6610
6611 // Render the input field
6612 printf(
6613 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
6614 esc_attr($popular_question_3),
6615 esc_attr(__('Enter Quick Question 3', 'mxchat'))
6616 );
6617
6618 // Add a description for the field
6619 echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
6620 }
6621
6622 public function mxchat_additional_popular_questions_callback() {
6623 $options = get_option('mxchat_options', []);
6624 $additional_questions = isset($options['additional_popular_questions'])
6625 ? $options['additional_popular_questions']
6626 : get_option('additional_popular_questions', array());
6627
6628 echo '<div id="mxchat-additional-questions-container">';
6629 if (!empty($additional_questions)) {
6630 foreach ($additional_questions as $index => $question) {
6631 printf(
6632 '<div class="mxchat-question-row">
6633 <input type="text" name="additional_popular_questions[]"
6634 value="%s"
6635 placeholder="%s"
6636 class="regular-text mxchat-question-input"
6637 data-question-index="%d" />
6638 <button type="button" class="button mxchat-remove-question"
6639 aria-label="%s">%s</button>
6640 </div>',
6641 esc_attr($question),
6642 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
6643 $index,
6644 esc_attr(__('Remove question', 'mxchat')),
6645 esc_html__('Remove', 'mxchat')
6646 );
6647 }
6648 } else {
6649 printf(
6650 '<div class="mxchat-question-row">
6651 <input type="text" name="additional_popular_questions[]"
6652 value=""
6653 placeholder="%s"
6654 class="regular-text mxchat-question-input"
6655 data-question-index="0" />
6656 <button type="button" class="button mxchat-remove-question"
6657 aria-label="%s">%s</button>
6658 </div>',
6659 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
6660 esc_attr(__('Remove question', 'mxchat')),
6661 esc_html__('Remove', 'mxchat')
6662 );
6663 }
6664 echo '</div>';
6665 printf(
6666 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
6667 esc_attr(__('Add question', 'mxchat')),
6668 esc_html__('Add Question', 'mxchat')
6669 );
6670 echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
6671 echo '</div>';
6672 }
6673
6674 public function mxchat_brave_api_key_callback() {
6675 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
6676
6677 echo '<div class="api-key-wrapper">';
6678 echo sprintf(
6679 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
6680 $brave_api_key
6681 );
6682 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6683 echo '</div>';
6684 echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
6685 }
6686
6687 public function mxchat_brave_image_count_callback() {
6688 $brave_image_count = isset($this->options['brave_image_count'])
6689 ? intval($this->options['brave_image_count'])
6690 : 4;
6691
6692 echo sprintf(
6693 '<input type="number" id="brave_image_count" name="brave_image_count"
6694 value="%d" min="1" max="6" class="small-text" />',
6695 $brave_image_count
6696 );
6697 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
6698 }
6699
6700 public function mxchat_brave_safe_search_callback() {
6701 $brave_safe_search = isset($this->options['brave_safe_search'])
6702 ? esc_attr($this->options['brave_safe_search'])
6703 : 'strict';
6704
6705 echo '<select id="brave_safe_search" name="brave_safe_search">';
6706 echo sprintf(
6707 '<option value="strict" %s>%s</option>',
6708 selected($brave_safe_search, 'strict', false),
6709 __('Strict', 'mxchat')
6710 );
6711 echo sprintf(
6712 '<option value="off" %s>%s</option>',
6713 selected($brave_safe_search, 'off', false),
6714 __('Off', 'mxchat')
6715 );
6716 echo '</select>';
6717 echo '<p class="description">' .
6718 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
6719 '</p>';
6720 }
6721
6722 public function mxchat_brave_news_count_callback() {
6723 $brave_news_count = isset($this->options['brave_news_count'])
6724 ? intval($this->options['brave_news_count'])
6725 : 3;
6726
6727 echo sprintf(
6728 '<input type="number" id="brave_news_count" name="brave_news_count"
6729 value="%d" min="1" max="10" class="small-text" />',
6730 $brave_news_count
6731 );
6732 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
6733 }
6734
6735 public function mxchat_brave_country_callback() {
6736 $brave_country = isset($this->options['brave_country'])
6737 ? esc_attr($this->options['brave_country'])
6738 : 'us';
6739
6740 echo sprintf(
6741 '<input type="text" id="brave_country" name="brave_country"
6742 value="%s" maxlength="2" class="small-text" />',
6743 $brave_country
6744 );
6745 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
6746 }
6747
6748 public function mxchat_brave_language_callback() {
6749 $brave_language = isset($this->options['brave_language'])
6750 ? esc_attr($this->options['brave_language'])
6751 : 'en';
6752
6753 echo sprintf(
6754 '<input type="text" id="brave_language" name="brave_language"
6755 value="%s" maxlength="2" class="small-text" />',
6756 $brave_language
6757 );
6758 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
6759 }
6760
6761
6762
6763
6764
6765 // Section Callback
6766 public function mxchat_pdf_intent_section_callback() {
6767 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
6768 }
6769
6770 public function mxchat_chat_toolbar_toggle_callback() {
6771 // Get chat toolbar toggle value with fallback
6772 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6773 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6774
6775 // Check if the plugin is activated (paid feature)
6776 $disabled = $this->is_activated ? '' : 'disabled';
6777 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6778
6779 echo '<div class="' . esc_attr($class) . '">';
6780
6781 // Output the toggle switch
6782 echo '<label class="toggle-switch">';
6783 echo sprintf(
6784 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
6785 esc_attr($checked),
6786 esc_attr($disabled)
6787 );
6788 echo '<span class="slider"></span>';
6789 echo '</label>';
6790
6791 // Pro feature overlay
6792 if (!$this->is_activated) {
6793 echo '<div class="pro-feature-overlay">';
6794 echo '<a href="https://mxchat.ai/" target="_blank">';
6795 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6796 echo '</a>';
6797 echo '</div>';
6798 }
6799
6800 echo '</div>';
6801 echo '<p class="description">' . esc_html__('Enable to display the chat toolbar, adding two icons below the chatbot input field for uploading PDF and Word documents (default is hidden).', 'mxchat') . '</p>';
6802 }
6803
6804 /**
6805 * Callback for PDF upload button toggle setting
6806 */
6807 public function mxchat_show_pdf_upload_button_callback() {
6808 // Get toggle value with fallback
6809 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6810 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
6811 // Check if the plugin is activated (paid feature)
6812 $disabled = $this->is_activated ? '' : 'disabled';
6813 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6814
6815 echo '<div class="' . esc_attr($class) . '">';
6816 // Output the toggle switch
6817 echo '<label class="toggle-switch">';
6818 echo sprintf(
6819 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s %s />',
6820 esc_attr($checked),
6821 esc_attr($disabled)
6822 );
6823 echo '<span class="slider"></span>';
6824 echo '</label>';
6825
6826 // Pro feature overlay if needed
6827 if (!$this->is_activated) {
6828 echo '<div class="pro-feature-overlay">';
6829 echo '<a href="https://mxchat.ai/" target="_blank">';
6830 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6831 echo '</a>';
6832 echo '</div>';
6833 }
6834
6835 echo '</div>';
6836 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6837 }
6838
6839 /**
6840 * Callback for Word upload button toggle setting
6841 */
6842 public function mxchat_show_word_upload_button_callback() {
6843 // Get toggle value with fallback
6844 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6845 $checked = ($show_word_button === 'on') ? 'checked' : '';
6846 // Check if the plugin is activated (paid feature)
6847 $disabled = $this->is_activated ? '' : 'disabled';
6848 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6849
6850 echo '<div class="' . esc_attr($class) . '">';
6851 // Output the toggle switch
6852 echo '<label class="toggle-switch">';
6853 echo sprintf(
6854 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s %s />',
6855 esc_attr($checked),
6856 esc_attr($disabled)
6857 );
6858 echo '<span class="slider"></span>';
6859 echo '</label>';
6860
6861 // Pro feature overlay if needed
6862 if (!$this->is_activated) {
6863 echo '<div class="pro-feature-overlay">';
6864 echo '<a href="https://mxchat.ai/" target="_blank">';
6865 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6866 echo '</a>';
6867 echo '</div>';
6868 }
6869
6870 echo '</div>';
6871 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6872 }
6873
6874 public function mxchat_pdf_intent_trigger_text_callback() {
6875 $disabled = $this->is_activated ? '' : 'disabled';
6876 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6877 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
6878
6879 echo '<div class="' . esc_attr($class) . '">';
6880 echo sprintf(
6881 '<textarea id="pdf_intent_trigger_text"
6882 name="pdf_intent_trigger_text"
6883 rows="3"
6884 cols="50"
6885 placeholder="%s"
6886 %s>%s</textarea>',
6887 esc_attr__('Enter trigger text', 'mxchat'),
6888 esc_attr($disabled),
6889 isset($this->options['pdf_intent_trigger_text'])
6890 ? esc_textarea($this->options['pdf_intent_trigger_text'])
6891 : esc_textarea($default_text)
6892 );
6893 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
6894
6895 if (!$this->is_activated) {
6896 echo '<div class="pro-feature-overlay">';
6897 echo '<a href="https://mxchat.ai/" target="_blank">';
6898 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6899 echo '</a>';
6900 echo '</div>';
6901 }
6902 echo '</div>';
6903 }
6904
6905 public function mxchat_pdf_intent_success_text_callback() {
6906 $disabled = $this->is_activated ? '' : 'disabled';
6907 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6908 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
6909
6910 echo '<div class="' . esc_attr($class) . '">';
6911 echo sprintf(
6912 '<textarea id="pdf_intent_success_text"
6913 name="pdf_intent_success_text"
6914 rows="3"
6915 cols="50"
6916 placeholder="%s"
6917 %s>%s</textarea>',
6918 esc_attr__('Enter success text', 'mxchat'),
6919 esc_attr($disabled),
6920 isset($this->options['pdf_intent_success_text'])
6921 ? esc_textarea($this->options['pdf_intent_success_text'])
6922 : esc_textarea($default_text)
6923 );
6924 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
6925
6926 if (!$this->is_activated) {
6927 echo '<div class="pro-feature-overlay">';
6928 echo '<a href="https://mxchat.ai/" target="_blank">';
6929 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6930 echo '</a>';
6931 echo '</div>';
6932 }
6933 echo '</div>';
6934 }
6935
6936 public function mxchat_pdf_intent_error_text_callback() {
6937 $disabled = $this->is_activated ? '' : 'disabled';
6938 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6939 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
6940
6941 echo '<div class="' . esc_attr($class) . '">';
6942 echo sprintf(
6943 '<textarea id="pdf_intent_error_text"
6944 name="pdf_intent_error_text"
6945 rows="3"
6946 cols="50"
6947 placeholder="%s"
6948 %s>%s</textarea>',
6949 esc_attr__('Enter error text', 'mxchat'),
6950 esc_attr($disabled),
6951 isset($this->options['pdf_intent_error_text'])
6952 ? esc_textarea($this->options['pdf_intent_error_text'])
6953 : esc_textarea($default_text)
6954 );
6955 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
6956
6957 if (!$this->is_activated) {
6958 echo '<div class="pro-feature-overlay">';
6959 echo '<a href="https://mxchat.ai/" target="_blank">';
6960 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6961 echo '</a>';
6962 echo '</div>';
6963 }
6964 echo '</div>';
6965 }
6966
6967 public function mxchat_pdf_max_pages_callback() {
6968 $disabled = $this->is_activated ? '' : 'disabled';
6969 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6970 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
6971
6972 echo '<div class="' . esc_attr($class) . '">';
6973 echo sprintf(
6974 '<input type="range"
6975 id="pdf_max_pages"
6976 name="pdf_max_pages"
6977 min="1"
6978 max="69"
6979 value="%d"
6980 %s
6981 class="range-slider" />',
6982 esc_attr($max_pages),
6983 esc_attr($disabled)
6984 );
6985 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
6986 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
6987
6988 if (!$this->is_activated) {
6989 echo '<div class="pro-feature-overlay">';
6990 echo '<a href="https://mxchat.ai/" target="_blank">';
6991 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6992 echo '</a>';
6993 echo '</div>';
6994 }
6995 echo '</div>';
6996 }
6997
6998 public function mxchat_live_agent_status_callback() {
6999 $disabled = $this->is_activated ? '' : 'disabled';
7000 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7001 $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
7002
7003 echo '<div class="' . esc_attr($class) . '">';
7004 echo '<label class="toggle-switch">';
7005 echo sprintf(
7006 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
7007 checked($status, 'on', false),
7008 esc_attr($disabled)
7009 );
7010 echo '<span class="slider"></span>';
7011 echo '</label>';
7012 echo '<label for="live_agent_status" class="mxchat-status-label">';
7013 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7014 echo '</label>';
7015
7016 if (!$this->is_activated) {
7017 echo '<div class="pro-feature-overlay">';
7018 echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
7019 echo '</div>';
7020 }
7021 echo '</div>';
7022 }
7023
7024
7025 // Away Message Callback
7026 public function mxchat_live_agent_away_message_callback() {
7027 $disabled = $this->is_activated ? '' : 'disabled';
7028 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7029 $message = isset($this->options['live_agent_away_message'])
7030 ? $this->options['live_agent_away_message']
7031 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7032
7033 echo '<div class="' . esc_attr($class) . '">';
7034 printf(
7035 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
7036 esc_attr($disabled),
7037 esc_textarea($message)
7038 );
7039 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7040
7041 if (!$this->is_activated) {
7042 echo '<div class="pro-feature-overlay">';
7043 echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
7044 echo '</div>';
7045 }
7046 echo '</div>';
7047 }
7048
7049 public function mxchat_live_agent_notification_message_callback() {
7050 $disabled = $this->is_activated ? '' : 'disabled';
7051 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7052 $message = isset($this->options['live_agent_notification_message'])
7053 ? $this->options['live_agent_notification_message']
7054 : __('Live agent has been notified.', 'mxchat');
7055
7056 echo '<div class="' . esc_attr($class) . '">';
7057 printf(
7058 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
7059 esc_attr($disabled),
7060 esc_textarea($message)
7061 );
7062 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7063
7064 if (!$this->is_activated) {
7065 echo '<div class="pro-feature-overlay">';
7066 echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
7067 echo '</div>';
7068 }
7069 echo '</div>';
7070 }
7071
7072 public function mxchat_live_agent_webhook_url_callback() {
7073 $disabled = $this->is_activated ? '' : 'disabled';
7074 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7075 $webhook_url = isset($this->options['live_agent_webhook_url'])
7076 ? esc_url($this->options['live_agent_webhook_url'])
7077 : esc_url(get_option('live_agent_webhook_url', ''));
7078
7079 echo '<div class="' . esc_attr($class) . '">';
7080 printf(
7081 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
7082 $webhook_url,
7083 esc_attr($disabled)
7084 );
7085 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7086 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7087
7088 if (!$this->is_activated) {
7089 echo '<div class="pro-feature-overlay">';
7090 echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
7091 echo '</div>';
7092 }
7093 echo '</div>';
7094 }
7095
7096 public function mxchat_similarity_threshold_callback() {
7097 // Load from mxchat_options array
7098 $options = get_option('mxchat_options', []);
7099
7100 // Get value from options array with default of 80
7101 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7102
7103 echo '<div class="slider-container">';
7104 echo sprintf(
7105 '<input type="range"
7106 id="similarity_threshold"
7107 name="similarity_threshold"
7108 min="20"
7109 max="85"
7110 step="1"
7111 value="%s"
7112 class="range-slider" />',
7113 esc_attr($threshold)
7114 );
7115 echo sprintf(
7116 '<span id="threshold_value" class="range-value">%s</span>',
7117 esc_html($threshold)
7118 );
7119 echo '</div>';
7120 echo '<p class="description">';
7121 echo esc_html__('Set the similarity threshold to balance accuracy; too high might limit your bot\'s ability to find relevant knowledge. If your bot is not matching content, move to lowest setting to test.', 'mxchat');
7122 echo '</p>';
7123 }
7124
7125
7126 public function mxchat_live_agent_secret_key_callback() {
7127 $disabled = $this->is_activated ? '' : 'disabled';
7128 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7129
7130 echo '<div class="' . esc_attr($class) . '">';
7131 printf(
7132 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
7133 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
7134 esc_attr($disabled)
7135 );
7136 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7137 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7138
7139 if (!$this->is_activated) {
7140 echo '<div class="pro-feature-overlay">';
7141 echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
7142 echo '</div>';
7143 }
7144 echo '</div>';
7145 }
7146
7147 public function mxchat_live_agent_bot_token_callback() {
7148 $disabled = $this->is_activated ? '' : 'disabled';
7149 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7150
7151 echo '<div class="' . esc_attr($class) . '">';
7152 printf(
7153 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
7154 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
7155 esc_attr($disabled)
7156 );
7157 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7158 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7159
7160 if (!$this->is_activated) {
7161 echo '<div class="pro-feature-overlay">';
7162 echo '<a href="https://mxchat.ai/" target="_blank"><img src="' . plugin_dir_url(__FILE__) . '../images/pro-only-dark.png" alt="' . esc_attr__('Pro Only', 'mxchat') . '" /></a>';
7163 echo '</div>';
7164 }
7165 echo '</div>';
7166 }
7167
7168 public function mxchat_enqueue_admin_assets() {
7169 wp_enqueue_style('wp-color-picker');
7170
7171 // Get the plugin version or file modification time for cache busting
7172 $plugin_version = '2.1.6'; // Replace this with your plugin's version
7173
7174 // File paths
7175 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
7176 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
7177 $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
7178 $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
7179 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
7180 $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
7181 $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
7182
7183 // Check if files exist and get modification times
7184 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
7185 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
7186 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
7187 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
7188 $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
7189 $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
7190 $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
7191
7192 $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js';
7193 $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version;
7194
7195 // Check current admin page
7196 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7197
7198 // Only enqueue on the prompts page
7199 if ($current_page === 'mxchat-prompts') {
7200 wp_enqueue_script(
7201 'mxchat-status-updater',
7202 plugin_dir_url(__FILE__) . '../js/admin-status.js',
7203 array('jquery'),
7204 $admin_status_js_version,
7205 true
7206 );
7207
7208 // Add the nonce for the status updater
7209 wp_localize_script(
7210 'mxchat-status-updater',
7211 'mxchat_status_data',
7212 array(
7213 'ajax_url' => admin_url('admin-ajax.php'),
7214 'nonce' => wp_create_nonce('mxchat_status_nonce')
7215 )
7216 );
7217 }
7218
7219 // Enqueue scripts and styles with corrected paths
7220 wp_enqueue_script(
7221 'mxchat-color-picker',
7222 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
7223 array('wp-color-picker'),
7224 $color_picker_version,
7225 true
7226 );
7227
7228 wp_enqueue_script(
7229 'mxchat-embedding-check',
7230 plugin_dir_url(__FILE__) . '../js/embedding-check.js',
7231 array(),
7232 $embedding_check_version,
7233 true
7234 );
7235
7236 wp_enqueue_script(
7237 'mxchat-admin-js',
7238 plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
7239 array('jquery'),
7240 $plugin_version,
7241 true
7242 );
7243
7244 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
7245 'ajax_url' => admin_url('admin-ajax.php'),
7246 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7247 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7248 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7249 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7250 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7251 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7252 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7253 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7254 'is_activated' => $this->is_activated ? '1' : '0',
7255 // Add these two new lines
7256 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7257 'status_refresh_interval' => 5000, // Update every 5 seconds
7258 ));
7259
7260 // Enqueue the admin CSS
7261 wp_enqueue_style(
7262 'mxchat-admin-css',
7263 plugin_dir_url(__FILE__) . '../css/admin-style.css',
7264 array(),
7265 $admin_css_version
7266 );
7267
7268 // Conditional enqueue for transcripts page
7269 if ($current_page === 'mxchat-transcripts') {
7270 wp_enqueue_style(
7271 'mxchat-chat-transcripts-css',
7272 plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
7273 array(),
7274 $transcripts_css_version
7275 );
7276
7277 wp_enqueue_script(
7278 'mxchat-transcripts-js',
7279 plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
7280 array('jquery'),
7281 $transcripts_js_version,
7282 true
7283 );
7284 }
7285
7286 // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages
7287 if (strpos($current_page, 'mxchat') !== false) {
7288 wp_enqueue_style(
7289 'mxchat-knowledge-css',
7290 plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
7291 array(),
7292 $knowledge_css_version
7293 );
7294 }
7295
7296 // Only enqueue intent-style.css on the mxchat-actions page
7297 if ($current_page === 'mxchat-actions') {
7298 wp_enqueue_style(
7299 'mxchat-intent-css',
7300 plugin_dir_url(__FILE__) . '../css/intent-style.css',
7301 array(),
7302 $intent_css_version
7303 );
7304 }
7305
7306 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
7307 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7308 'ajax_url' => admin_url('admin-ajax.php'),
7309 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7310 ));
7311
7312 // Localize the script for color picker and settings
7313 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7314 'ajax_url' => admin_url('admin-ajax.php'),
7315 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
7316 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
7317 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
7318 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
7319 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
7320 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
7321 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
7322 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
7323 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
7324 'icon_color' => $this->options['icon_color'] ?? '#fff',
7325 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
7326 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
7327 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
7328 'loops_api_key' => $this->options['loops_api_key'] ?? '',
7329 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
7330 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7331 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7332 'pdf_intent_trigger_text' => $this->options['pdf_intent_trigger_text'] ?? esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
7333 'pdf_intent_success_text' => $this->options['pdf_intent_success_text'] ?? esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
7334 'pdf_intent_error_text' => $this->options['pdf_intent_error_text'] ?? esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
7335 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7336 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7337 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7338 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7339 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7340 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7341 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7342 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7343 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7344 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7345 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7346 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7347 ));
7348 }
7349
7350
7351 public function mxchat_sanitize($input) {
7352 $new_input = array();
7353
7354 if (isset($input['api_key'])) {
7355 $new_input['api_key'] = sanitize_text_field($input['api_key']);
7356 }
7357
7358 if (isset($input['similarity_threshold'])) {
7359 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
7360 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
7361 }
7362
7363 if (isset($input['xai_api_key'])) {
7364 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
7365 }
7366
7367 if (isset($input['claude_api_key'])) {
7368 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
7369 }
7370
7371 if (isset($input['deepseek_api_key'])) {
7372 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
7373 }
7374
7375 if (isset($input['gemini_api_key'])) {
7376 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7377 }
7378
7379 if (isset($input['enable_woocommerce_integration'])) {
7380 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
7381 }
7382
7383 if (isset($input['privacy_toggle'])) {
7384 $new_input['privacy_toggle'] = $input['privacy_toggle'];
7385 }
7386
7387 if (isset($input['complianz_toggle'])) {
7388 $new_input['complianz_toggle'] = $input['complianz_toggle'];
7389 }
7390
7391 // Handle custom privacy text input
7392 if (isset($input['privacy_text'])) {
7393 // Allow basic HTML for links
7394 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
7395 }
7396
7397 if (isset($input['system_prompt_instructions'])) {
7398 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
7399 }
7400
7401 if (isset($input['mxchat_pro_email'])) {
7402 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
7403 }
7404
7405 if (isset($input['mxchat_activation_key'])) {
7406 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
7407 }
7408
7409 if (isset($input['append_to_body'])) {
7410 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
7411 }
7412
7413 if (isset($input['top_bar_title'])) {
7414 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
7415 }
7416
7417 if (isset($input['ai_agent_text'])) {
7418 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7419 }
7420
7421 if (isset($input['enable_email_block'])) {
7422 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
7423 }
7424
7425 if (isset($input['email_blocker_header_content'])) {
7426 // wp_kses_post() allows standard HTML tags permitted by WordPress
7427 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
7428 }
7429 if (isset($input['email_blocker_button_text'])) {
7430 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
7431 }
7432
7433 if (isset($input['intro_message'])) {
7434 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
7435 }
7436
7437 if (isset($input['input_copy'])) {
7438 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
7439 }
7440
7441 if (isset($input['rate_limit_message'])) {
7442 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
7443 }
7444
7445 if (isset($input['rate_limit_logged_out'])) {
7446 $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
7447 $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
7448
7449 if (in_array($rate_limit, $allowed_limits, true)) {
7450 $new_input['rate_limit_logged_out'] = $rate_limit;
7451 } else {
7452 $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
7453 }
7454 }
7455
7456 // Handle role rate limits
7457 if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
7458 $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
7459 $new_input['role_rate_limits'] = array();
7460
7461 foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
7462 $rate_limit = sanitize_text_field($rate_limit);
7463 if (in_array($rate_limit, $allowed_limits, true)) {
7464 $new_input['role_rate_limits'][$role_id] = $rate_limit;
7465 } else {
7466 $new_input['role_rate_limits'][$role_id] = '100'; // Default
7467 }
7468 }
7469 }
7470
7471 if (isset($input['pre_chat_message'])) {
7472 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
7473 }
7474
7475 if (isset($input['voyage_api_key'])) {
7476 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7477 }
7478
7479 // Add to your sanitize function
7480 if (isset($input['embedding_model'])) {
7481 $allowed_models = array(
7482 'text-embedding-ada-002',
7483 'text-embedding-3-small',
7484 'text-embedding-3-large',
7485 'voyage-3-large'
7486 );
7487 if (in_array($input['embedding_model'], $allowed_models)) {
7488 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7489 }
7490 }
7491
7492 if (isset($input['model'])) {
7493 $allowed_models = array(
7494 'gemini-2.0-flash',
7495 'gemini-2.0-flash-lite',
7496 'gemini-1.5-pro',
7497 'gemini-1.5-flash',
7498 'grok-3-beta',
7499 'grok-3-fast-beta',
7500 'grok-3-mini-beta',
7501 'grok-3-mini-fast-beta',
7502 'grok-2',
7503 'deepseek-chat',
7504 'claude-3-7-sonnet-20250219',
7505 'claude-3-5-sonnet-20241022',
7506 'claude-3-opus-20240229',
7507 'claude-3-sonnet-20240229',
7508 'claude-3-haiku-20240307',
7509 'gpt-4o',
7510 'gpt-4.1-2025-04-14',
7511 'gpt-4o-mini',
7512 'gpt-4-turbo',
7513 'gpt-4',
7514 'gpt-3.5-turbo',
7515 );
7516 if (in_array($input['model'], $allowed_models)) {
7517 $new_input['model'] = sanitize_text_field($input['model']);
7518 }
7519 }
7520
7521 if (isset($input['close_button_color'])) {
7522 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7523 }
7524
7525 if (isset($input['chatbot_bg_color'])) {
7526 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7527 }
7528
7529 if (isset($input['woocommerce_consumer_key'])) {
7530 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
7531 }
7532
7533 if (isset($input['woocommerce_consumer_secret'])) {
7534 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
7535 }
7536
7537 if (isset($input['user_message_bg_color'])) {
7538 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7539 }
7540
7541 if (isset($input['user_message_font_color'])) {
7542 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7543 }
7544
7545 if (isset($input['bot_message_bg_color'])) {
7546 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7547 }
7548
7549 if (isset($input['bot_message_font_color'])) {
7550 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7551 }
7552
7553 if (isset($input['live_agent_message_bg_color'])) {
7554 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7555 }
7556
7557 if (isset($input['live_agent_message_font_color'])) {
7558 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
7559 }
7560
7561 if (isset($input['mode_indicator_bg_color'])) {
7562 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
7563 }
7564
7565 if (isset($input['mode_indicator_font_color'])) {
7566 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
7567 }
7568
7569 if (isset($input['toolbar_icon_color'])) {
7570 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
7571 }
7572
7573 if (isset($input['top_bar_bg_color'])) {
7574 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
7575 }
7576
7577 if (isset($input['send_button_font_color'])) {
7578 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
7579 }
7580
7581 if (isset($input['chatbot_background_color'])) {
7582 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
7583 }
7584
7585 if (isset($input['icon_color'])) {
7586 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
7587 }
7588
7589 if (isset($input['custom_icon'])) {
7590 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
7591 }
7592
7593 if (isset($input['title_icon'])) {
7594 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
7595 }
7596
7597 if (isset($input['chat_input_font_color'])) {
7598 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
7599 }
7600
7601 // Sanitize link_target_toggle
7602 if (isset($input['link_target_toggle'])) {
7603 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
7604 }
7605
7606 // Sanitize Loops API Key
7607 if (isset($input['loops_api_key'])) {
7608 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
7609 }
7610
7611 if (isset($input['chat_persistence_toggle'])) {
7612 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
7613 }
7614
7615 if (isset($input['popular_question_1'])) {
7616 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
7617 }
7618
7619 if (isset($input['popular_question_2'])) {
7620 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
7621 }
7622
7623 if (isset($input['popular_question_3'])) {
7624 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
7625 }
7626
7627 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
7628 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
7629 }
7630
7631 // Sanitize Loops Mailing List
7632 if (isset($input['loops_mailing_list'])) {
7633 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
7634 }
7635
7636 // Sanitize Triggered Phrase Response
7637 if (isset($input['triggered_phrase_response'])) {
7638 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
7639 }
7640
7641 if (isset($input['email_capture_response'])) {
7642 $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
7643 }
7644
7645 // Sanitize Brave Search Settings
7646 if (isset($input['brave_api_key'])) {
7647 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
7648 }
7649
7650 if (isset($input['brave_image_count'])) {
7651 $image_count = intval($input['brave_image_count']);
7652 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
7653 }
7654
7655 if (isset($input['brave_safe_search'])) {
7656 $allowed = array('strict', 'off');
7657 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
7658 }
7659
7660 if (isset($input['brave_news_count'])) {
7661 $news_count = intval($input['brave_news_count']);
7662 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
7663 }
7664
7665 if (isset($input['brave_country'])) {
7666 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
7667 }
7668
7669 if (isset($input['brave_language'])) {
7670 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
7671 }
7672
7673 if (isset($input['chat_toolbar_toggle'])) {
7674 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
7675 }
7676
7677 // Sanitize PDF upload button toggle
7678 if (isset($input['show_pdf_upload_button'])) {
7679 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7680 } else {
7681 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7682 }
7683
7684 // Sanitize Word upload button toggle
7685 if (isset($input['show_word_upload_button'])) {
7686 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7687 } else {
7688 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7689 }
7690
7691 if (isset($input['pdf_intent_trigger_text'])) {
7692 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
7693 }
7694
7695 if (isset($input['pdf_intent_success_text'])) {
7696 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
7697 }
7698
7699 if (isset($input['pdf_intent_error_text'])) {
7700 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
7701 }
7702
7703 if (isset($input['pdf_max_pages'])) {
7704 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
7705 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
7706 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
7707 }
7708 }
7709
7710 if (isset($input['live_agent_webhook_url'])) {
7711 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
7712 }
7713 if (isset($input['live_agent_secret_key'])) {
7714 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
7715 }
7716
7717 // Live Agent Integration
7718 if (isset($input['live_agent_bot_token'])) {
7719 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
7720 }
7721
7722 if (isset($input['live_agent_status'])) {
7723 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
7724 }
7725 if (isset($input['live_agent_away_message'])) {
7726 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
7727 }
7728 if (isset($input['live_agent_notification_message'])) {
7729 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
7730 }
7731
7732 return $new_input;
7733 }
7734
7735
7736 // Method to append the chatbot to the body
7737 public function mxchat_append_chatbot_to_body() {
7738 $options = get_option('mxchat_options');
7739 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
7740 echo do_shortcode('[mxchat_chatbot floating="yes"]');
7741 }
7742 }
7743
7744
7745
7746
7747
7748 private function mxchat_fetch_loops_mailing_lists($api_key) {
7749 $url = 'https://app.loops.so/api/v1/lists';
7750 $response = wp_remote_get($url, array(
7751 'headers' => array(
7752 'Authorization' => 'Bearer ' . $api_key,
7753 'Content-Type' => 'application/json'
7754 )
7755 ));
7756
7757 if (is_wp_error($response)) {
7758 return array();
7759 }
7760
7761 $body = wp_remote_retrieve_body($response);
7762 $lists = json_decode($body, true);
7763
7764 return isset($lists) && is_array($lists) ? $lists : array();
7765 }
7766
7767
7768
7769
7770
7771 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
7772 if (empty($vec1) || empty($vec2)) {
7773 return 0.0;
7774 }
7775
7776 $dot_product = 0.0;
7777 $norm_a = 0.0;
7778 $norm_b = 0.0;
7779
7780 for ($i = 0; $i < count($vec1); $i++) {
7781 $dot_product += $vec1[$i] * $vec2[$i];
7782 $norm_a += pow($vec1[$i], 2);
7783 $norm_b += pow($vec2[$i], 2);
7784 }
7785
7786 if ($norm_a == 0.0 || $norm_b == 0.0) {
7787 return 0.0;
7788 } else {
7789 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
7790 }
7791 }
7792
7793
7794 /**
7795 * Update your existing display_admin_notices function to show notices on all MXChat pages
7796 */
7797 public function display_admin_notices() {
7798 // Check if we're on a MXChat admin page
7799 $screen = get_current_screen();
7800 if (!$screen || strpos($screen->base, 'mxchat') === false) {
7801 return;
7802 }
7803
7804 //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
7805
7806 // Check for error notices
7807 $error_notice = get_transient('mxchat_admin_notice_error');
7808 if ($error_notice) {
7809 //error_log('Found error transient: ' . $error_notice);
7810 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
7811 delete_transient('mxchat_admin_notice_error');
7812 //error_log('Displayed and deleted error transient');
7813 } else {
7814 //error_log('No error transient found');
7815 }
7816
7817 // Check for success notices
7818 $success_notice = get_transient('mxchat_admin_notice_success');
7819 if ($success_notice) {
7820 //error_log('Found success transient: ' . $success_notice);
7821 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
7822 delete_transient('mxchat_admin_notice_success');
7823 //error_log('Displayed and deleted success transient');
7824 }
7825
7826 // Check for info notices
7827 $info_notice = get_transient('mxchat_admin_notice_info');
7828 if ($info_notice) {
7829 //error_log('Found info transient: ' . $info_notice);
7830 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
7831 delete_transient('mxchat_admin_notice_info');
7832 //error_log('Displayed and deleted info transient');
7833 }
7834
7835 // Display active processing status
7836 $this->display_processing_status();
7837 }
7838
7839
7840
7841 /**
7842 * Display current processing status
7843 */
7844 private function display_processing_status() {
7845 $pdf_url = get_transient('mxchat_last_pdf_url');
7846 $sitemap_url = get_transient('mxchat_last_sitemap_url');
7847
7848 if (!$pdf_url && !$sitemap_url) {
7849 return;
7850 }
7851
7852 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
7853 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
7854
7855 if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) {
7856 echo '<div class="notice notice-error is-dismissible">';
7857 echo '<p><strong>' . esc_html__('Sitemap Processing Error:', 'mxchat') . '</strong> ' . esc_html($sitemap_status['error']) . '</p>';
7858 echo '</div>';
7859 }
7860
7861 if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) {
7862 echo '<div class="notice notice-error is-dismissible">';
7863 echo '<p><strong>' . esc_html__('PDF Processing Error:', 'mxchat') . '</strong> ' . esc_html($pdf_status['error']) . '</p>';
7864 echo '</div>';
7865 }
7866 }
7867
7868
7869 }
7870 ?>
7871