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

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

7,866 lines 335.5 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 error_log(sprintf('[MXCHAT-PDF] Error storing content in database for page %d: %s',
2436 $i + 1, $db_result->get_error_message()));
2437 throw new Exception(sprintf('Failed to store content in database for page %d: %s',
2438 $i + 1, $db_result->get_error_message()));
2439 }
2440
2441 // Update progress with sanitized data
2442 $status['processed_pages'] = absint($i + 1);
2443 $status['last_update'] = time();
2444 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2445 }
2446
2447 // Schedule next batch if needed
2448 if ($end_page < $total_pages) {
2449 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array(
2450 'pdf_path' => $pdf_path,
2451 'pdf_url' => $pdf_url,
2452 'total_pages' => $total_pages,
2453 'batch_size' => $batch_size,
2454 'batch_pause' => $batch_pause
2455 ));
2456 } else {
2457 // Processing complete
2458 $status['status'] = 'complete';
2459 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2460 if (file_exists($pdf_path)) {
2461 wp_delete_file($pdf_path);
2462 }
2463 }
2464
2465 } catch (\Exception $e) {
2466 //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage()));
2467
2468 // Get current status to update it
2469 $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url));
2470 $status = get_transient($status_key);
2471
2472 if (!$status || !is_array($status)) {
2473 $status = array(
2474 'total_pages' => $total_pages,
2475 'processed_pages' => 0,
2476 'status' => 'error',
2477 'error' => sanitize_text_field($e->getMessage()),
2478 'last_update' => time()
2479 );
2480 } else {
2481 $status['status'] = 'error';
2482 $status['error'] = sanitize_text_field($e->getMessage());
2483 $status['last_update'] = time();
2484 }
2485
2486 set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS);
2487
2488 if (file_exists($pdf_path)) {
2489 wp_delete_file($pdf_path);
2490 }
2491 }
2492 }
2493
2494 public function get_pdf_processing_status($pdf_url) {
2495 $pdf_url = esc_url_raw($pdf_url);
2496 $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url)));
2497
2498 if (!$status || !is_array($status)) {
2499 return false;
2500 }
2501
2502 // Check for stalled processing (no updates for 5 minutes)
2503 if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) {
2504 $status['status'] = 'error';
2505 $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat');
2506
2507 // Save the updated status
2508 set_transient(
2509 sanitize_key('mxchat_pdf_status_' . md5($pdf_url)),
2510 array_map('sanitize_text_field', $status),
2511 DAY_IN_SECONDS
2512 );
2513 }
2514
2515 $result = array(
2516 'total_pages' => absint($status['total_pages']),
2517 'processed_pages' => absint($status['processed_pages']),
2518 'percentage' => ($status['total_pages'] > 0)
2519 ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100)
2520 : 0,
2521 'status' => sanitize_text_field($status['status']),
2522 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat')
2523 );
2524
2525 // Add error message if present
2526 if (isset($status['error']) && !empty($status['error'])) {
2527 $result['error'] = sanitize_text_field($status['error']);
2528 }
2529
2530 return $result;
2531 }
2532
2533
2534 public function mxchat_handle_sitemap_submission() {
2535 // Start logging the submission process
2536 //error_log('[MXCHAT-URL] ===== Starting URL submission process =====');
2537
2538 // Check if the form was submitted and verify permissions
2539 if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) {
2540 //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly');
2541 wp_die(esc_html__('Unauthorized access', 'mxchat'));
2542 }
2543
2544 // Verify nonce
2545 //error_log('[MXCHAT-URL] Verifying nonce');
2546 check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce');
2547
2548 // Validate URL
2549 if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) {
2550 //error_log('[MXCHAT-URL] Error: Empty or missing URL');
2551 set_transient('mxchat_admin_notice_error',
2552 esc_html__('Please provide a valid URL.', 'mxchat'),
2553 30
2554 );
2555 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2556 exit;
2557 }
2558
2559 $submitted_url = esc_url_raw($_POST['sitemap_url']);
2560 //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url);
2561
2562 // Validate API key first
2563 $options = get_option('mxchat_options');
2564 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
2565
2566 if (strpos($selected_model, 'voyage') === 0) {
2567 $api_key = $options['voyage_api_key'] ?? '';
2568 $provider_name = 'Voyage AI';
2569 } else {
2570 $api_key = $options['api_key'] ?? '';
2571 $provider_name = 'OpenAI';
2572 }
2573
2574 if (empty($api_key)) {
2575 $error_message = sprintf(
2576 esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'),
2577 $provider_name
2578 );
2579 //error_log('[MXCHAT-URL] Error: ' . $error_message);
2580 set_transient('mxchat_admin_notice_error', $error_message, 30);
2581 //error_log('[MXCHAT-URL] Set error transient: ' . $error_message);
2582 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2583 exit;
2584 }
2585
2586 //error_log('[MXCHAT-URL] Fetching URL content');
2587 $response = wp_remote_get($submitted_url, array('timeout' => 30));
2588
2589 if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
2590 $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response);
2591 //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message);
2592 set_transient('mxchat_admin_notice_error',
2593 sprintf(
2594 esc_html__('Failed to fetch the URL: %s', 'mxchat'),
2595 esc_html($error_message)
2596 ),
2597 30
2598 );
2599 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2600 exit;
2601 }
2602
2603 $content_type = wp_remote_retrieve_header($response, 'content-type');
2604 //error_log('[MXCHAT-URL] Content type: ' . $content_type);
2605 $body_content = wp_remote_retrieve_body($response);
2606
2607 if (empty($body_content)) {
2608 //error_log('[MXCHAT-URL] Error: Empty response body');
2609 set_transient('mxchat_admin_notice_error',
2610 esc_html__('Empty response received from URL.', 'mxchat'),
2611 30
2612 );
2613 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2614 exit;
2615 }
2616 //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes');
2617
2618 // Handle PDF URL
2619 if ($this->is_pdf_url($submitted_url, $response)) {
2620 //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base');
2621 $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response);
2622 //error_log('[MXCHAT-URL] PDF handling result: ' . $result);
2623
2624 if ($result === 'scheduled') {
2625 set_transient(
2626 'mxchat_last_pdf_url',
2627 sanitize_text_field($submitted_url),
2628 DAY_IN_SECONDS
2629 );
2630 set_transient('mxchat_admin_notice_info',
2631 esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2632 30
2633 );
2634 } else {
2635 set_transient('mxchat_admin_notice_error',
2636 esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result),
2637 30
2638 );
2639 }
2640
2641 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2642 exit;
2643 }
2644
2645 // Handle Sitemap XML
2646 if (strpos($content_type, 'xml') !== false || strpos($body_content, '<urlset') !== false) {
2647 //error_log('[MXCHAT-URL] Detected XML content, processing as sitemap');
2648 libxml_use_internal_errors(true);
2649 $xml = simplexml_load_string($body_content);
2650 $xml_errors = libxml_get_errors();
2651 libxml_clear_errors();
2652
2653 if ($xml === false || !empty($xml_errors)) {
2654 //error_log('[MXCHAT-URL] Error: Invalid XML format');
2655 if (!empty($xml_errors)) {
2656 foreach ($xml_errors as $error) {
2657 //error_log('[MXCHAT-URL] XML Error: ' . $error->message);
2658 }
2659 }
2660
2661 set_transient('mxchat_admin_notice_error',
2662 esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'),
2663 30
2664 );
2665 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2666 exit;
2667 }
2668
2669 //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base');
2670 $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url);
2671 //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result);
2672
2673 if ($result === 'scheduled') {
2674 set_transient(
2675 'mxchat_last_sitemap_url',
2676 sanitize_text_field($submitted_url),
2677 DAY_IN_SECONDS
2678 );
2679 set_transient('mxchat_admin_notice_info',
2680 esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'),
2681 30
2682 );
2683 } else {
2684 // Return to the admin page without a redirect for better error display
2685 // The error is already stored in the sitemap status transient
2686 set_transient('mxchat_admin_notice_error',
2687 esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'),
2688 30
2689 );
2690 }
2691
2692 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2693 exit;
2694 }
2695
2696 // Handle Regular URL
2697 // Handle Regular URL
2698 //error_log('[MXCHAT-URL] Processing as regular webpage');
2699 $page_content = $this->mxchat_extract_main_content($body_content);
2700 //error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes');
2701
2702 $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content);
2703 //error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes');
2704
2705 if (empty($sanitized_content)) {
2706 //error_log('[MXCHAT-URL] Error: No valid content after sanitization');
2707
2708 // Set both transients - the error notice and the URL status
2709 set_transient('mxchat_admin_notice_error',
2710 esc_html__('No valid content found on the provided URL.', 'mxchat'),
2711 30
2712 );
2713
2714 // Set URL status transient
2715 set_transient('mxchat_single_url_status', [
2716 'url' => $submitted_url,
2717 'timestamp' => current_time('mysql'),
2718 'status' => 'failed',
2719 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat')
2720 ], DAY_IN_SECONDS);
2721
2722 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2723 exit;
2724 }
2725
2726 //error_log('[MXCHAT-URL] Generating embedding for content');
2727 $embedding_vector = $this->mxchat_generate_embedding($sanitized_content);
2728
2729 // Check if embedding_vector is a string (error message)
2730 if (is_string($embedding_vector)) {
2731 //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector);
2732 $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector);
2733 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2734
2735 // Set both transients
2736 set_transient('mxchat_admin_notice_error', $error_message, 30);
2737
2738 // Set URL status transient
2739 set_transient('mxchat_single_url_status', [
2740 'url' => $submitted_url,
2741 'timestamp' => current_time('mysql'),
2742 'status' => 'failed',
2743 'error' => $error_message
2744 ], DAY_IN_SECONDS);
2745
2746 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2747 exit;
2748 }
2749
2750 if (is_array($embedding_vector)) {
2751 //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions');
2752
2753 $db_result = MxChat_Utils::submit_content_to_db(
2754 $sanitized_content,
2755 $submitted_url,
2756 $this->options['api_key']
2757 );
2758
2759 if (is_wp_error($db_result)) {
2760 //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message());
2761 $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message());
2762 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2763
2764 // Set both transients
2765 set_transient('mxchat_admin_notice_error', $error_message, 30);
2766
2767 // Set URL status transient
2768 set_transient('mxchat_single_url_status', [
2769 'url' => $submitted_url,
2770 'timestamp' => current_time('mysql'),
2771 'status' => 'failed',
2772 'error' => $error_message
2773 ], DAY_IN_SECONDS);
2774
2775 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2776 exit;
2777 }
2778
2779 //error_log('[MXCHAT-URL] Successfully stored content in database');
2780 $success_message = esc_html__('URL content successfully submitted!', 'mxchat');
2781 //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message);
2782
2783 // Set both transients
2784 set_transient('mxchat_admin_notice_success', $success_message, 30);
2785
2786 // Set URL status transient with success
2787 set_transient('mxchat_single_url_status', [
2788 'url' => $submitted_url,
2789 'timestamp' => current_time('mysql'),
2790 'status' => 'complete',
2791 'content_length' => strlen($sanitized_content),
2792 'embedding_dimensions' => count($embedding_vector)
2793 ], DAY_IN_SECONDS);
2794
2795 } else {
2796 //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector));
2797 $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat');
2798 //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message);
2799
2800 // Set both transients
2801 set_transient('mxchat_admin_notice_error', $error_message, 30);
2802
2803 // Set URL status transient
2804 set_transient('mxchat_single_url_status', [
2805 'url' => $submitted_url,
2806 'timestamp' => current_time('mysql'),
2807 'status' => 'failed',
2808 'error' => $error_message
2809 ], DAY_IN_SECONDS);
2810 }
2811
2812 //error_log('[MXCHAT-URL] ===== Completed URL submission process =====');
2813 wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts')));
2814 exit;
2815 }
2816 /**
2817 * Get the status of the last single URL submission
2818 */
2819 private function get_single_url_status() {
2820 $status = get_transient('mxchat_single_url_status');
2821 if (!$status) {
2822 return null;
2823 }
2824
2825 // Add human-readable time
2826 if (isset($status['timestamp'])) {
2827 $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat');
2828 }
2829
2830 return $status;
2831 }
2832
2833 private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) {
2834 // Clear any single URL status when starting sitemap processing
2835 delete_transient('mxchat_single_url_status');
2836 if (!current_user_can('manage_options')) {
2837 //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat'));
2838 return false;
2839 }
2840
2841 try {
2842 $sitemap_url = esc_url_raw($sitemap_url);
2843
2844 if (!$xml || !is_object($xml)) {
2845 throw new Exception(__('Invalid XML object provided', 'mxchat'));
2846 }
2847
2848 // Add embedding validation before processing
2849 // Test embedding with a small sample text to verify API key is working
2850 $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working.");
2851
2852 // Check if test_result is a string (error message) rather than an array (valid embedding)
2853 if (is_string($test_result)) {
2854 //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result);
2855
2856 // Store the error in the status transient so it can be displayed later
2857 $status_data = array(
2858 'total_urls' => 0,
2859 'processed_urls' => 0,
2860 'status' => 'error',
2861 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result,
2862 'last_update' => time()
2863 );
2864
2865 set_transient(
2866 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2867 array_map('sanitize_text_field', $status_data),
2868 DAY_IN_SECONDS
2869 );
2870
2871 throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result);
2872 }
2873
2874 // Make sure it's an array (valid embedding)
2875 if (!is_array($test_result)) {
2876 //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result));
2877 throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat'));
2878 }
2879
2880 $urls = [];
2881 foreach ($xml->url as $url_element) {
2882 $url = esc_url_raw((string)$url_element->loc);
2883 if ($url) {
2884 $urls[] = $url;
2885 }
2886 }
2887
2888 $total_urls = absint(count($urls));
2889
2890 if ($total_urls < 1) {
2891 throw new Exception(__('No valid URLs found in sitemap', 'mxchat'));
2892 }
2893
2894 wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array(
2895 'urls' => $urls,
2896 'sitemap_url' => $sitemap_url,
2897 'total_urls' => $total_urls,
2898 'batch_size' => absint(10),
2899 'batch_pause' => absint(5)
2900 ));
2901
2902 $status_data = array(
2903 'total_urls' => $total_urls,
2904 'processed_urls' => 0,
2905 'status' => 'processing',
2906 'last_update' => time()
2907 );
2908
2909 set_transient(
2910 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2911 array_map('sanitize_text_field', $status_data),
2912 DAY_IN_SECONDS
2913 );
2914
2915 return __('scheduled', 'mxchat');
2916
2917 } catch (\Exception $e) {
2918 $error_message = $e->getMessage();
2919 //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message)));
2920
2921 // Store the sitemap URL and error in transients so they can be displayed
2922 set_transient(
2923 'mxchat_last_sitemap_url',
2924 sanitize_text_field($sitemap_url),
2925 DAY_IN_SECONDS
2926 );
2927
2928 $status_data = array(
2929 'total_urls' => 0,
2930 'processed_urls' => 0,
2931 'status' => 'error',
2932 'error' => $error_message,
2933 'last_update' => time()
2934 );
2935
2936 set_transient(
2937 sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)),
2938 array_map('sanitize_text_field', $status_data),
2939 DAY_IN_SECONDS
2940 );
2941
2942 return $error_message;
2943 }
2944 }
2945
2946 public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) {
2947 // Validate inputs
2948 $sitemap_url = esc_url_raw($sitemap_url);
2949 $total_urls = absint($total_urls);
2950 $batch_size = absint($batch_size);
2951 $batch_pause = absint($batch_pause);
2952
2953 if (!is_array($urls) || empty($urls)) {
2954 return;
2955 }
2956
2957 try {
2958 $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url));
2959 $status = get_transient($status_key);
2960
2961 if (!$status || !is_array($status)) {
2962 throw new Exception('Invalid status data retrieved from transient');
2963 }
2964
2965 // Initialize failed_urls array if it doesn't exist
2966 if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) {
2967 $status['failed_urls_list'] = [];
2968 }
2969
2970 $start_url = absint($status['processed_urls']);
2971 $end_url = min($start_url + $batch_size, $total_urls);
2972 $instance = new self();
2973
2974 // Track failures in batch
2975 $batch_stats = [
2976 'processed' => 0,
2977 'failed' => 0,
2978 'last_error' => '',
2979 'embedding_errors' => 0 // Track specifically embedding errors
2980 ];
2981
2982 // Check embedding configuration with first URL
2983 if ($start_url === 0) {
2984 $page_url = esc_url_raw($urls[0]);
2985 $page_response = wp_remote_get($page_url);
2986
2987 if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) {
2988 $page_html = wp_remote_retrieve_body($page_response);
2989 $page_content = $instance->mxchat_extract_main_content($page_html);
2990 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
2991
2992 if (!empty($sanitized_content)) {
2993 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
2994
2995 // Check if embedding_vector is a string (error message)
2996 if (is_string($embedding_vector)) {
2997 throw new Exception('Embedding generation failed: ' . $embedding_vector);
2998 }
2999
3000 // Make sure it's an array (valid embedding)
3001 if (!is_array($embedding_vector)) {
3002 throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector));
3003 }
3004 }
3005 }
3006 }
3007
3008 for ($i = $start_url; $i < $end_url; $i++) {
3009 $page_url = esc_url_raw($urls[$i]);
3010 $page_response = wp_remote_get($page_url);
3011
3012 if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) {
3013 $batch_stats['failed']++;
3014 $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response);
3015 $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message;
3016
3017 // Add to failed URLs list with error message
3018 $status['failed_urls_list'][] = [
3019 'url' => $page_url,
3020 'error' => $error_message,
3021 'time' => time()
3022 ];
3023
3024 continue;
3025 }
3026
3027 $page_html = wp_remote_retrieve_body($page_response);
3028 $page_content = $instance->mxchat_extract_main_content($page_html);
3029 $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content);
3030
3031 if (!empty($sanitized_content)) {
3032 $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content);
3033
3034 // Check if embedding_vector is a string (error message)
3035 if (is_string($embedding_vector)) {
3036 $batch_stats['failed']++;
3037 $batch_stats['embedding_errors']++;
3038 $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector;
3039
3040 // Add to failed URLs list with error message
3041 $status['failed_urls_list'][] = [
3042 'url' => $page_url,
3043 'error' => 'Embedding error: ' . $embedding_vector,
3044 'time' => time()
3045 ];
3046
3047 // If we have multiple embedding errors, stop processing
3048 if ($batch_stats['embedding_errors'] >= 10) {
3049 throw new Exception('Multiple embedding failures detected: ' . $embedding_vector);
3050 }
3051 continue;
3052 }
3053
3054 // Check if it's an array (valid embedding)
3055 if (is_array($embedding_vector)) {
3056 $options = get_option('mxchat_options');
3057 $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']);
3058
3059 if (is_wp_error($submission_result)) {
3060 $batch_stats['failed']++;
3061 $batch_stats['last_error'] = $submission_result->get_error_message();
3062
3063 // Add to failed URLs list with error message
3064 $status['failed_urls_list'][] = [
3065 'url' => $page_url,
3066 'error' => 'Database submission error: ' . $submission_result->get_error_message(),
3067 'time' => time()
3068 ];
3069
3070 continue;
3071 }
3072
3073 $batch_stats['processed']++;
3074 } else {
3075 $batch_stats['failed']++;
3076 $batch_stats['embedding_errors']++;
3077 $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector);
3078
3079 // Add to failed URLs list with error message
3080 $status['failed_urls_list'][] = [
3081 'url' => $page_url,
3082 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector),
3083 'time' => time()
3084 ];
3085
3086 // If we have multiple embedding errors, stop processing
3087 if ($batch_stats['embedding_errors'] >= 10) {
3088 throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.');
3089 }
3090 }
3091 }
3092
3093 $status['processed_urls'] = absint($i + 1);
3094 $status['last_update'] = time();
3095 $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed'];
3096 $status['last_error'] = $batch_stats['last_error'];
3097
3098 // Limit the number of failed URLs we store to prevent transient size issues
3099 if (count($status['failed_urls_list']) > 100) {
3100 $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100);
3101 }
3102
3103 set_transient($status_key, $status, DAY_IN_SECONDS);
3104 }
3105
3106 // If all URLs in this batch failed, stop processing
3107 if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) {
3108 $status['status'] = 'error';
3109 $status['error'] = sprintf(
3110 'Processing stopped: %d consecutive failures. Last error: %s',
3111 $batch_stats['failed'],
3112 $batch_stats['last_error']
3113 );
3114 set_transient($status_key, $status, DAY_IN_SECONDS);
3115 return;
3116 }
3117
3118 if ($end_url < $total_urls) {
3119 wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array(
3120 'urls' => $urls,
3121 'sitemap_url' => $sitemap_url,
3122 'total_urls' => $total_urls,
3123 'batch_size' => $batch_size,
3124 'batch_pause' => $batch_pause,
3125 ));
3126 } else {
3127 $status['status'] = 'complete';
3128 set_transient($status_key, $status, DAY_IN_SECONDS);
3129 }
3130 } catch (\Exception $e) {
3131 $status['status'] = 'error';
3132 $status['error'] = $e->getMessage();
3133 set_transient($status_key, $status, DAY_IN_SECONDS);
3134 }
3135 }
3136
3137 private function mxchat_sanitize_content_for_api($content) {
3138 //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...');
3139
3140 // Remove script, style tags, and HTML comments
3141 $content = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $content);
3142 $content = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $content);
3143 $content = preg_replace('/<!--(.|\s)*?-->/', '', $content);
3144
3145 // Remove all HTML tags and decode HTML entities
3146 $content = wp_strip_all_tags($content);
3147 $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5);
3148
3149 // Trim and normalize whitespace
3150 $content = trim(preg_replace('/\s+/', ' ', $content));
3151
3152 // Remove control characters (which can cause database issues)
3153 $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content);
3154
3155 // Remove NULL bytes which can cause database errors
3156 $content = str_replace("\0", "", $content);
3157
3158 // Ensure valid UTF-8 encoding
3159 $content = wp_check_invalid_utf8($content);
3160
3161 // Remove any extremely long strings without spaces (often garbage)
3162 $content = preg_replace('/\S{300,}/', ' ', $content);
3163
3164 // Replace problematic characters that often cause database issues
3165 $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters
3166
3167 // Replace any remaining potentially problematic characters with spaces
3168 $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content);
3169
3170 // Limit to reasonable length if needed
3171 $max_length = 65000; // Just under MySQL TEXT field limit
3172 if (strlen($content) > $max_length) {
3173 $content = substr($content, 0, $max_length);
3174 }
3175
3176 //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...');
3177 return $content;
3178 }
3179 private static function mxchat_extract_main_content($html) {
3180 if (empty($html)) {
3181 return '';
3182 }
3183 try {
3184 $dom = new DOMDocument;
3185 libxml_use_internal_errors(true); // Suppress HTML parsing errors
3186 @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
3187 $xpath = new DOMXPath($dom);
3188
3189 // For debugging purposes
3190 $debugEnabled = false; // Set to true to enable debugging output
3191 $debug = function($message) use ($debugEnabled) {
3192 if ($debugEnabled) {
3193 //error_log('[MXCHAT-DEBUG] ' . $message);
3194 }
3195 };
3196
3197 // Direct targeting for Gerow theme posts
3198 $post_text = $xpath->query('//div[contains(@class, "post-text")]');
3199 if ($post_text && $post_text->length > 0) {
3200 $debug("Found post-text directly");
3201 $content = '';
3202 foreach ($post_text as $node) {
3203 $content .= $dom->saveHTML($node);
3204 }
3205 if (!empty($content)) {
3206 $debug("Returning post-text content");
3207 return $content;
3208 }
3209 }
3210
3211 // Try to get the blog details content which contains the post-text
3212 $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]');
3213 if ($blog_details && $blog_details->length > 0) {
3214 $debug("Found blog-details-content");
3215 $content = '';
3216 foreach ($blog_details as $node) {
3217 $content .= $dom->saveHTML($node);
3218 }
3219 if (!empty($content)) {
3220 $debug("Returning blog-details-content");
3221 return $content;
3222 }
3223 }
3224
3225 // Try to get the article which contains the blog details
3226 $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]');
3227 if ($article && $article->length > 0) {
3228 $debug("Found article with blog-details-wrap");
3229 $content = '';
3230 foreach ($article as $node) {
3231 $content .= $dom->saveHTML($node);
3232 }
3233 if (!empty($content)) {
3234 $debug("Returning article content");
3235 return $content;
3236 }
3237 }
3238
3239 // Try even broader with the blog-item-wrap
3240 $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]');
3241 if ($blog_item && $blog_item->length > 0) {
3242 $debug("Found blog-item-wrap");
3243 $content = '';
3244 foreach ($blog_item as $node) {
3245 $content .= $dom->saveHTML($node);
3246 }
3247 if (!empty($content)) {
3248 $debug("Returning blog-item-wrap content");
3249 return $content;
3250 }
3251 }
3252
3253 // Specific Gerow theme path
3254 $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]');
3255 if ($gerow_path && $gerow_path->length > 0) {
3256 $debug("Found Gerow theme path to post-text");
3257 $content = '';
3258 foreach ($gerow_path as $node) {
3259 $content .= $dom->saveHTML($node);
3260 }
3261 if (!empty($content)) {
3262 $debug("Returning Gerow post-text content");
3263 return $content;
3264 }
3265 }
3266
3267 // Generic blog post selectors
3268 $selectors = [
3269 // Blog post specific selectors
3270 '//div[contains(@class, "post-text")]',
3271 '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]',
3272 '//div[contains(@class, "blog-details-content")]',
3273 '//article[contains(@class, "blog-details-wrap")]',
3274 '//div[contains(@class, "entry-content")]',
3275 '//div[contains(@class, "blog-content")]',
3276 '//div[contains(@class, "blog-item-wrap")]',
3277
3278 // More general content selectors
3279 '//div[contains(@class, "page__content")]',
3280 '//div[contains(@class, "elementor-widget-container")]',
3281 '//div[contains(@class, "elementor-text-editor")]',
3282 '//div[contains(@class, "elementor-widget-text-editor")]',
3283 '//*[contains(@class, "entry-content")]',
3284 '//*[contains(@class, "post-content")]',
3285 '//*[contains(@class, "article-content")]',
3286 '//*[@id="content"]',
3287 '//*[@id="main-content"]',
3288 '//section[contains(@class, "blog-area")]',
3289 '//article',
3290 '//main',
3291 '//div[contains(@class, "content")]'
3292 ];
3293
3294 // First handle Elementor content
3295 $debug("Checking for Elementor content");
3296 $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]');
3297 if ($elementor_widgets && $elementor_widgets->length > 0) {
3298 $debug("Found Elementor widgets");
3299 $combined_content = '';
3300 foreach ($elementor_widgets as $widget) {
3301 $widget_content = $dom->saveHTML($widget);
3302 if (!empty($widget_content)) {
3303 $combined_content .= $widget_content;
3304 }
3305 }
3306 if (!empty($combined_content)) {
3307 $debug("Returning Elementor content");
3308 return $combined_content;
3309 }
3310 }
3311
3312 // Try standard selectors one by one
3313 foreach ($selectors as $selector) {
3314 $debug("Trying selector: " . $selector);
3315 $nodes = $xpath->query($selector);
3316 if ($nodes && $nodes->length > 0) {
3317 $debug("Found matches for selector: " . $selector);
3318 $content = '';
3319 foreach ($nodes as $node) {
3320 $content .= $dom->saveHTML($node);
3321 }
3322 if (!empty($content)) {
3323 $debug("Returning content from selector: " . $selector);
3324 return $content;
3325 }
3326 }
3327 }
3328
3329 // Manual regex fallback for post-text if DOM methods fail
3330 $debug("Trying regex fallback");
3331 if (preg_match('/<div class="post-text">(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) {
3332 $debug("Found post-text via regex");
3333 return '<div class="post-text">' . $matches[1] . '</div>';
3334 }
3335
3336 // Try to extract the blog section as a whole
3337 $blog_section = $xpath->query('//section[contains(@class, "blog-area")]');
3338 if ($blog_section && $blog_section->length > 0) {
3339 $debug("Found blog-area section");
3340 $content = '';
3341 foreach ($blog_section as $node) {
3342 $content .= $dom->saveHTML($node);
3343 }
3344 if (!empty($content)) {
3345 $debug("Returning blog-area section content");
3346 return $content;
3347 }
3348 }
3349
3350 // Fallback: Return the body content if no specific selector matches
3351 $debug("Using body fallback");
3352 $body = $dom->getElementsByTagName('body');
3353 if ($body->length > 0) {
3354 return $dom->saveHTML($body->item(0));
3355 }
3356
3357 // Last resort: return the original HTML
3358 $debug("Returning original HTML");
3359 return $html;
3360 } catch (Exception $e) {
3361 //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage());
3362 return $html; // Return original HTML if parsing fails
3363 } finally {
3364 libxml_clear_errors();
3365 }
3366 }
3367
3368
3369 public function get_sitemap_processing_status($sitemap_url) {
3370 $sitemap_url = esc_url_raw($sitemap_url);
3371 $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)));
3372
3373 if (!$status || !is_array($status)) {
3374 return false;
3375 }
3376
3377 return array(
3378 'total_urls' => absint($status['total_urls']),
3379 'processed_urls' => absint($status['processed_urls']),
3380 'failed_urls' => absint($status['failed_urls'] ?? 0),
3381 'percentage' => ($status['total_urls'] > 0)
3382 ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100)
3383 : 0,
3384 'status' => sanitize_text_field($status['status']),
3385 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'),
3386 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '',
3387 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '',
3388 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array()
3389 );
3390 }
3391 public function ajax_get_status_updates() {
3392 // Verify the request
3393 check_ajax_referer('mxchat_status_nonce', 'nonce');
3394
3395 // Get the status just like in your admin page
3396 $pdf_url = get_transient('mxchat_last_pdf_url');
3397 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3398 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
3399 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
3400
3401 // Check for true processing status, not just presence of status
3402 $is_active_processing =
3403 ($sitemap_status && $sitemap_status['status'] === 'processing') ||
3404 ($pdf_status && $pdf_status['status'] === 'processing');
3405
3406 // Get single URL status, but only if no sitemap/PDF is processing
3407 $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false;
3408
3409 // Only clear transients for completed processes, not error states
3410 if ($pdf_status && $pdf_status['status'] === 'complete') {
3411 delete_transient('mxchat_last_pdf_url');
3412 $pdf_status = false;
3413 }
3414 if ($sitemap_status && $sitemap_status['status'] === 'complete') {
3415 delete_transient('mxchat_last_sitemap_url');
3416 $sitemap_status = false;
3417 }
3418
3419 // Return JSON response with the status data
3420 wp_send_json(array(
3421 'pdf_status' => $pdf_status,
3422 'sitemap_status' => $sitemap_status,
3423 'single_url_status' => $single_url_status,
3424 'is_processing' => $is_active_processing
3425 ));
3426 }
3427 public function mxchat_stop_processing() {
3428 // Verify permissions
3429 if (!current_user_can('manage_options')) {
3430 wp_die(esc_html__('Unauthorized access', 'mxchat'));
3431 }
3432
3433 // Verify nonce
3434 check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce');
3435
3436 // Get the last sitemap URL and clear its transient
3437 $sitemap_url = get_transient('mxchat_last_sitemap_url');
3438 if ($sitemap_url) {
3439 delete_transient('mxchat_sitemap_status_' . md5($sitemap_url));
3440 delete_transient('mxchat_last_sitemap_url');
3441 }
3442
3443 // Get the last PDF URL and clear its transient
3444 $pdf_url = get_transient('mxchat_last_pdf_url');
3445 if ($pdf_url) {
3446 delete_transient('mxchat_pdf_status_' . md5($pdf_url));
3447 delete_transient('mxchat_last_pdf_url');
3448 }
3449
3450 // Unschedule any pending sitemap events
3451 $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls');
3452 if ($timestamp) {
3453 wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls');
3454 }
3455
3456 // Redirect back with a success message
3457 set_transient('mxchat_admin_notice_success',
3458 esc_html__('Processing has been stopped successfully.', 'mxchat'),
3459 30
3460 );
3461 wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts'));
3462 exit;
3463 }
3464
3465
3466 public function mxchat_handle_product_change($post_id, $post, $update) {
3467 if ($post->post_type !== 'product') {
3468 return;
3469 }
3470
3471 if ($post->post_status === 'publish') {
3472 add_action('shutdown', function() use ($post_id) {
3473 $product = wc_get_product($post_id);
3474 if ($product) {
3475 $this->mxchat_store_product_embedding($product);
3476 }
3477 });
3478 }
3479 }
3480 private function mxchat_store_product_embedding($product) {
3481 if (!isset($this->options['enable_woocommerce_integration']) ||
3482 !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) {
3483 return;
3484 }
3485
3486 $source_url = get_permalink($product->get_id());
3487
3488 // Build description
3489 $regular_price = $product->get_regular_price();
3490 $sale_price = $product->get_sale_price();
3491 $description = $product->get_description() . "\n\n" .
3492 esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" .
3493 esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" .
3494 esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" .
3495 esc_html__('SKU:', 'mxchat') . " " . $product->get_sku();
3496
3497 // Generate embedding vector
3498 $embedding_vector = $this->mxchat_generate_embedding($description);
3499 if (!is_array($embedding_vector)) {
3500 return;
3501 }
3502
3503 // Check for Pinecone addon and its settings
3504 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
3505 $use_pinecone = false;
3506
3507 if ($pinecone_settings && is_array($pinecone_settings)) {
3508 $use_pinecone = (
3509 isset($pinecone_settings['mxchat_use_pinecone']) &&
3510 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
3511 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
3512 !empty($pinecone_settings['mxchat_pinecone_host']) &&
3513 !empty($pinecone_settings['mxchat_pinecone_index']) &&
3514 !empty($pinecone_settings['mxchat_pinecone_environment'])
3515 );
3516 }
3517
3518 if ($use_pinecone) {
3519 $result = $this->store_in_pinecone_main(
3520 $embedding_vector,
3521 $description,
3522 $source_url,
3523 $pinecone_settings['mxchat_pinecone_api_key'],
3524 $pinecone_settings['mxchat_pinecone_environment'],
3525 $pinecone_settings['mxchat_pinecone_index']
3526 );
3527
3528 if (!$result['success']) {
3529 // Fallback to WordPress DB if Pinecone storage fails
3530 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3531 }
3532 } else {
3533 // Use WordPress DB storage
3534 $this->store_in_wordpress_db($description, $source_url, $embedding_vector);
3535 }
3536 }
3537 public function mxchat_handle_product_delete($post_id) {
3538 if (get_post_type($post_id) !== 'product') {
3539 return;
3540 }
3541
3542 $source_url = get_permalink($post_id);
3543
3544 // Check for Pinecone addon and its settings
3545 $pinecone_settings = get_option('mxchat_pinecone_addon_options');
3546 $use_pinecone = false;
3547
3548 if ($pinecone_settings && is_array($pinecone_settings)) {
3549 $use_pinecone = (
3550 isset($pinecone_settings['mxchat_use_pinecone']) &&
3551 $pinecone_settings['mxchat_use_pinecone'] === '1' &&
3552 !empty($pinecone_settings['mxchat_pinecone_api_key']) &&
3553 !empty($pinecone_settings['mxchat_pinecone_host']) &&
3554 !empty($pinecone_settings['mxchat_pinecone_index']) &&
3555 !empty($pinecone_settings['mxchat_pinecone_environment'])
3556 );
3557 }
3558
3559 if ($use_pinecone) {
3560 $this->delete_from_pinecone(
3561 array($source_url),
3562 $pinecone_settings['mxchat_pinecone_api_key'],
3563 $pinecone_settings['mxchat_pinecone_environment'],
3564 $pinecone_settings['mxchat_pinecone_index']
3565 );
3566 } else {
3567 // Fallback to WordPress DB deletion
3568 global $wpdb;
3569 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
3570 $wpdb->delete($table_name, array('source_url' => $source_url), array('%s'));
3571 }
3572 }
3573
3574
3575 /**
3576 * Delete vectors from Pinecone by source URL
3577 *
3578 * @param array $urls Array of source URLs to delete
3579 * @param string $api_key Pinecone API key
3580 * @param string $environment Pinecone environment
3581 * @param string $index_name Pinecone index name
3582 * @return array Associative array with 'success' boolean and 'message' string
3583 */
3584 private function delete_from_pinecone($urls, $api_key, $environment, $index_name) {
3585 // Get the Pinecone host from options (matching your store_in_pinecone_main pattern)
3586 $options = get_option('mxchat_pinecone_addon_options');
3587 $host = $options['mxchat_pinecone_host'] ?? '';
3588
3589 if (empty($host)) {
3590 return array(
3591 'success' => false,
3592 'message' => 'Pinecone host is not configured. Please set the host in your settings.'
3593 );
3594 }
3595
3596 // Build API endpoint using the configured host
3597 $api_endpoint = "https://{$host}/vectors/delete";
3598
3599 // Create vector IDs from URLs (matching your store method's ID generation)
3600 $vector_ids = array_map('md5', $urls);
3601
3602 // Prepare the delete request body
3603 $request_body = array(
3604 'ids' => $vector_ids,
3605 'filter' => array(
3606 'source_url' => array(
3607 '$in' => $urls
3608 )
3609 )
3610 );
3611
3612 // Make the deletion request
3613 $response = wp_remote_post($api_endpoint, array(
3614 'headers' => array(
3615 'Api-Key' => $api_key,
3616 'accept' => 'application/json',
3617 'content-type' => 'application/json'
3618 ),
3619 'body' => wp_json_encode($request_body),
3620 'timeout' => 30,
3621 'data_format' => 'body'
3622 ));
3623
3624 // Handle WordPress HTTP API errors
3625 if (is_wp_error($response)) {
3626 return array(
3627 'success' => false,
3628 'message' => $response->get_error_message()
3629 );
3630 }
3631
3632 // Check response status
3633 $response_code = wp_remote_retrieve_response_code($response);
3634 if ($response_code !== 200) {
3635 $body = wp_remote_retrieve_body($response);
3636 return array(
3637 'success' => false,
3638 'message' => sprintf(
3639 'Pinecone API error (HTTP %d): %s',
3640 $response_code,
3641 $body
3642 )
3643 );
3644 }
3645
3646 // Parse response body
3647 $body = wp_remote_retrieve_body($response);
3648 $response_data = json_decode($body, true);
3649
3650 // Final validation of the response
3651 if (json_last_error() !== JSON_ERROR_NONE) {
3652 return array(
3653 'success' => false,
3654 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg()
3655 );
3656 }
3657
3658 return array(
3659 'success' => true,
3660 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids))
3661 );
3662 }
3663
3664
3665
3666
3667
3668 public function mxchat_create_activation_page() {
3669 $license_status = get_option('mxchat_license_status', 'inactive');
3670 $license_error = get_option('mxchat_license_error', '');
3671 ?>
3672 <div class="wrap mxchat-admin-activation">
3673 <div class="mxchat-pro-hero">
3674 <h1 class="pro-title">
3675 <span class="pro-gradient-text">Activate</span> MxChat Pro
3676 </h1>
3677 <p class="pro-subtitle">
3678 <?php esc_html_e('Enter your license key to unlock premium features, advanced AI capabilities, and priority support.', 'mxchat'); ?>
3679 </p>
3680 </div>
3681
3682 <?php if ($license_status === 'inactive' && !empty($license_error)): ?>
3683 <div class="error notice">
3684 <p><?php echo esc_html($license_error); ?></p>
3685 </div>
3686 <?php endif; ?>
3687
3688 <form id="mxchat-activation-form" class="mxchat-pro-form" style="<?php echo $license_status === 'active' ? 'display: none;' : ''; ?>">
3689 <div class="mxchat-pro-form-container">
3690 <table class="form-table">
3691 <tr valign="top">
3692 <th scope="row"><?php esc_html_e('Email Address', 'mxchat'); ?></th>
3693 <td>
3694 <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 />
3695 </td>
3696 </tr>
3697 <tr valign="top">
3698 <th scope="row"><?php esc_html_e('Activation Key', 'mxchat'); ?></th>
3699 <td>
3700 <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 />
3701 </td>
3702 </tr>
3703 </table>
3704 <?php if ($license_status !== 'active'): ?>
3705 <div class="mxchat-pro-button-container">
3706 <button type="submit" id="activate_license_button" class="button button-primary mxchat-pro-button"><?php esc_html_e('Activate License', 'mxchat'); ?></button>
3707 <div id="mxchat-activation-spinner" class="mxchat-activation-spinner" style="display: none;"></div>
3708 </div>
3709 <?php endif; ?>
3710 </div>
3711 </form>
3712 <!-- License Status Display -->
3713 <div class="mxchat-pro-status">
3714 <h3><?php esc_html_e('License Status:', 'mxchat'); ?>
3715 <span id="mxchat-license-status" class="mxchat-status-badge <?php echo $license_status; ?>">
3716 <?php echo $license_status === 'active' ? esc_html__('Active', 'mxchat') : esc_html__('Inactive', 'mxchat'); ?>
3717 </span>
3718 </h3>
3719 </div>
3720
3721 </div>
3722 <?php
3723 }
3724
3725 public function mxchat_actions_page_html() {
3726 if (!current_user_can('manage_options')) {
3727 return;
3728 }
3729
3730 // Keep existing data fetching logic
3731 global $wpdb;
3732 $table_name = $wpdb->prefix . 'mxchat_intents';
3733 $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
3734 $per_page = 20;
3735 $offset = ($page - 1) * $per_page;
3736
3737 // Success message
3738 if (isset($_GET['updated']) && $_GET['updated'] === 'true') {
3739 echo '<div class="notice notice-success is-dismissible"><p>' .
3740 esc_html__('Action updated successfully.', 'mxchat') .
3741 '</p></div>';
3742 }
3743
3744 // Filtering logic
3745 $where = '1=1';
3746 $search_term = isset($_GET['s']) ? trim($_GET['s']) : '';
3747 $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : '';
3748
3749 if ($search_term) {
3750 $search_term_like = '%' . $wpdb->esc_like($search_term) . '%';
3751 $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)',
3752 $search_term_like, $search_term_like);
3753 }
3754
3755 if ($callback_filter) {
3756 $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter);
3757 }
3758
3759 // Pagination
3760 $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where");
3761 $total_pages = ceil($total_intents / $per_page);
3762
3763 // Get intents (now called actions)
3764 $actions = $wpdb->get_results($wpdb->prepare(
3765 "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d",
3766 $per_page, $offset
3767 ));
3768
3769 // Get callbacks
3770 $available_callbacks = $this->mxchat_get_available_callbacks();
3771
3772 ?>
3773 <div class="wrap mxchat-wrapper">
3774 <!-- Hero Section -->
3775 <div class="mxchat-hero">
3776 <h1 class="mxchat-main-title">
3777 <span class="mxchat-gradient-text">Actions</span> Manager
3778 </h1>
3779 <p class="mxchat-hero-subtitle">
3780 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
3781 </p>
3782 </div>
3783
3784 <!-- Actions Header with Search and Filter -->
3785 <div class="mxchat-actions-header">
3786 <div class="mxchat-actions-filters">
3787 <form method="get" class="mxchat-search-form">
3788 <input type="hidden" name="page" value="mxchat-actions">
3789 <div class="mxchat-search-group">
3790 <span class="dashicons dashicons-search"></span>
3791 <input type="text" name="s" class="mxchat-search-input"
3792 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3793 value="<?php echo esc_attr($search_term); ?>">
3794 </div>
3795 <select name="callback_filter" class="mxchat-action-filter">
3796 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3797 <?php foreach ($available_callbacks as $function => $callback_data) :
3798 $label = $callback_data['label']; ?>
3799 <option value="<?php echo esc_attr($function); ?>"
3800 <?php selected($callback_filter, $function); ?>>
3801 <?php echo esc_html($label); ?>
3802 </option>
3803 <?php endforeach; ?>
3804 </select>
3805 <button type="submit" class="mxchat-button-secondary">
3806 <?php esc_html_e('Filter', 'mxchat'); ?>
3807 </button>
3808 </form>
3809 </div>
3810 <div class="mxchat-actions-controls">
3811 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3812 <span class="dashicons dashicons-plus-alt"></span>
3813 <?php esc_html_e('Add New Action', 'mxchat'); ?>
3814 </button>
3815 </div>
3816 </div>
3817
3818 <!-- Actions Grid Layout - All actions in a single grid -->
3819 <div class="mxchat-actions-grid">
3820 <div class="mxchat-cards-container">
3821 <?php if (!empty($actions)) : ?>
3822 <?php foreach ($actions as $action) :
3823 $callback_function = $action->callback_function;
3824 $callback_label = isset($available_callbacks[$callback_function]['label'])
3825 ? $available_callbacks[$callback_function]['label']
3826 : $callback_function;
3827 $threshold_value = isset($action->similarity_threshold)
3828 ? round($action->similarity_threshold * 100)
3829 : 85;
3830
3831 // Check if this is a form action
3832 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3833
3834 // Get action status (enabled/disabled) - default to true if column doesn't exist
3835 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3836 ?>
3837 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3838 <div class="mxchat-card-header">
3839 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3840 <div class="mxchat-card-toggle">
3841 <label class="mxchat-switch">
3842 <input type="checkbox" class="mxchat-action-toggle"
3843 data-action-id="<?php echo esc_attr($action->id); ?>"
3844 <?php checked($is_enabled); ?>>
3845 <span class="mxchat-slider round"></span>
3846 </label>
3847 </div>
3848 </div>
3849
3850 <div class="mxchat-card-body">
3851 <div class="mxchat-card-description">
3852 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3853 <?php echo esc_html($callback_label); ?>
3854 </div>
3855
3856 <div class="mxchat-card-phrases">
3857 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3858 <div class="mxchat-phrases-preview">
3859 <?php
3860 // Check if the helper function exists, otherwise use a simple substring
3861 if (method_exists($this, 'get_trimmed_phrases')) {
3862 echo esc_html($this->get_trimmed_phrases($action->phrases));
3863 } else {
3864 echo esc_html(strlen($action->phrases) > 100 ?
3865 substr($action->phrases, 0, 97) . '...' :
3866 $action->phrases);
3867 }
3868 ?>
3869 </div>
3870 </div>
3871
3872 <div class="mxchat-threshold-control">
3873 <div class="mxchat-threshold-label">
3874 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3875 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3876 </div>
3877 </div>
3878 </div>
3879
3880 <div class="mxchat-card-footer">
3881 <?php if (!$is_form_action) : ?>
3882 <button type="button"
3883 class="mxchat-button-secondary mxchat-edit-button"
3884 data-action-id="<?php echo esc_attr($action->id); ?>"
3885 data-phrases="<?php echo esc_attr($action->phrases); ?>"
3886 data-label="<?php echo esc_attr($action->intent_label); ?>"
3887 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3888 data-callback-function="<?php echo esc_attr($action->callback_function); ?>">
3889 <span class="dashicons dashicons-edit"></span>
3890 <?php esc_html_e('Edit', 'mxchat'); ?>
3891 </button>
3892 <form method="post"
3893 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3894 class="mxchat-delete-form"
3895 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3896 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3897 <input type="hidden" name="action" value="mxchat_delete_intent">
3898 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3899 <button type="submit" class="mxchat-button-text mxchat-delete-button">
3900 <span class="dashicons dashicons-trash"></span>
3901 <?php esc_html_e('Delete', 'mxchat'); ?>
3902 </button>
3903 </form>
3904 <?php else :
3905 preg_match('/Form (\d+)/', $action->intent_label, $matches);
3906 $form_id = isset($matches[1]) ? $matches[1] : '';
3907 ?>
3908 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3909 class="mxchat-button-primary">
3910 <span class="dashicons dashicons-feedback"></span>
3911 <?php esc_html_e('Edit Form', 'mxchat'); ?>
3912 </a>
3913 <?php endif; ?>
3914 </div>
3915 </div>
3916 <?php endforeach; ?>
3917 <?php else : ?>
3918 <!-- If no actions found -->
3919 <div class="mxchat-no-actions">
3920 <div class="mxchat-empty-state">
3921 <span class="dashicons dashicons-format-chat"></span>
3922 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
3923 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
3924 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
3925 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
3926 </button>
3927 </div>
3928 </div>
3929 <?php endif; ?>
3930 </div>
3931 </div>
3932
3933 <?php if ($total_pages > 1) : ?>
3934 <div class="mxchat-pagination">
3935 <?php
3936 echo paginate_links(array(
3937 'base' => add_query_arg('paged', '%#%'),
3938 'format' => '',
3939 'prev_text' => __('&laquo; Previous', 'mxchat'),
3940 'next_text' => __('Next &raquo;', 'mxchat'),
3941 'total' => $total_pages,
3942 'current' => $page
3943 ));
3944 ?>
3945 </div>
3946 <?php endif; ?>
3947
3948 <!-- Add/Edit Action Modal with Step-Based Approach -->
3949 <!-- Complete Modal HTML with Defined Groups Variable -->
3950 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
3951 <div class="mxchat-modal-content">
3952 <span class="mxchat-modal-close">&times;</span>
3953
3954 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3955 <!-- Dynamic nonce field -->
3956 <div id="action-nonce-container">
3957 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
3958 </div>
3959 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
3960 <input type="hidden" name="intent_id" id="edit_action_id" value="">
3961 <input type="hidden" name="callback_function" id="callback_function" value="">
3962
3963 <!-- Step 1: Action Type Selection -->
3964 <div id="mxchat-action-step-1" class="mxchat-action-step active">
3965 <div class="mxchat-step-indicator">
3966 <div class="mxchat-step-number">1</div>
3967 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
3968 </div>
3969
3970 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
3971 <div class="mxchat-action-type-search">
3972 <span class="dashicons dashicons-search"></span>
3973 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
3974 </div>
3975
3976 <?php
3977 // Get the callbacks - IMPORTANT: Define the $groups variable here
3978 $groups = $this->mxchat_get_available_callbacks(true, true);
3979 ?>
3980
3981 <div class="mxchat-action-type-categories">
3982 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
3983 <?php
3984 // Get unique categories from the defined groups
3985 foreach ($groups as $group_label => $group_callbacks) :
3986 $category_slug = sanitize_title($group_label);
3987 ?>
3988 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
3989 <?php endforeach; ?>
3990 </div>
3991
3992 <div class="mxchat-action-types-grid">
3993 <?php
3994 // Generate action cards from available callbacks
3995 foreach ($groups as $group_label => $group_callbacks) :
3996 $category_slug = sanitize_title($group_label);
3997
3998 foreach ($group_callbacks as $function => $data) :
3999 $label = $data['label'];
4000 $pro_only = $data['pro_only'];
4001 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
4002 $description = isset($data['description']) ? $data['description'] : '';
4003 $is_addon = isset($data['addon']) && $data['addon'] !== false;
4004 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
4005 $is_installed = isset($data['installed']) ? $data['installed'] : true;
4006
4007 // Determine card status and styling
4008 $card_class = 'mxchat-action-type-card';
4009 $icon_class = 'mxchat-action-type-icon';
4010 $status_badge = '';
4011
4012 if ($pro_only && !$this->is_activated) {
4013 // Pro feature but no Pro license
4014 $icon_class .= ' pro-feature';
4015 $status_badge = '<span class="mxchat-pro-badge">' . esc_html__('Pro', 'mxchat') . '</span>';
4016 }
4017
4018 if ($is_addon && !$is_installed) {
4019 // Add-on not installed
4020 $card_class .= ' not-installed';
4021 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
4022 }
4023
4024 // Default description if none provided
4025 if (empty($description)) {
4026 $description = sprintf(
4027 esc_html__('Use the %s action in your chatbot', 'mxchat'),
4028 $label
4029 );
4030 }
4031 ?>
4032 <div class="<?php echo esc_attr($card_class); ?>"
4033 data-category="<?php echo esc_attr($category_slug); ?>"
4034 data-value="<?php echo esc_attr($function); ?>"
4035 data-label="<?php echo esc_attr($label); ?>"
4036 data-pro="<?php echo $pro_only ? 'true' : 'false'; ?>"
4037 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
4038 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>">
4039 <div class="<?php echo esc_attr($icon_class); ?>">
4040 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
4041 </div>
4042 <div class="mxchat-action-type-info">
4043 <h4><?php echo esc_html($label); ?></h4>
4044 <p><?php echo esc_html($description); ?></p>
4045 <?php if (!empty($status_badge)) : ?>
4046 <?php echo $status_badge; ?>
4047 <?php endif; ?>
4048
4049 <?php if ($is_addon && !$is_installed) : ?>
4050 <div class="mxchat-addon-info">
4051 <?php echo esc_html(sprintf(
4052 __('Requires %s', 'mxchat'),
4053 $addon_name
4054 )); ?>
4055 </div>
4056 <?php endif; ?>
4057 </div>
4058 </div>
4059 <?php endforeach;
4060 endforeach; ?>
4061 </div>
4062 </div>
4063
4064 <div class="mxchat-modal-actions">
4065 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4066 <?php esc_html_e('Cancel', 'mxchat'); ?>
4067 </button>
4068 </div>
4069 </div>
4070
4071 <!-- Step 2: Action Configuration -->
4072 <div id="mxchat-action-step-2" class="mxchat-action-step">
4073 <div class="mxchat-step-indicator">
4074 <div class="mxchat-step-number">2</div>
4075 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
4076 </div>
4077
4078 <div class="mxchat-selected-action">
4079 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
4080 <span class="dashicons dashicons-arrow-left-alt"></span>
4081 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
4082 </button>
4083 <div class="mxchat-selected-action-info">
4084 <div id="selected-action-icon" class="mxchat-action-type-icon">
4085 <span class="dashicons dashicons-admin-generic"></span>
4086 </div>
4087 <div class="mxchat-selected-action-details">
4088 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
4089 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
4090 </div>
4091 </div>
4092 </div>
4093
4094 <div class="mxchat-form-group">
4095 <label for="intent_label">
4096 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
4097 </label>
4098 <input name="intent_label" type="text" id="intent_label" required
4099 class="mxchat-intent-input"
4100 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
4101 </div>
4102
4103 <div class="mxchat-form-group">
4104 <label for="phrases">
4105 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
4106 </label>
4107 <textarea name="phrases" id="action_phrases" rows="5" required
4108 class="mxchat-intent-textarea"
4109 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
4110 </div>
4111
4112 <div class="mxchat-form-group">
4113 <label for="similarity_threshold">
4114 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
4115 <span class="mxchat-threshold-value-display">85%</span>
4116 </label>
4117 <div class="mxchat-slider-group modal-slider">
4118 <input type="range"
4119 name="similarity_threshold"
4120 id="similarity_threshold"
4121 min="70"
4122 max="95"
4123 value="85"
4124 class="mxchat-intent-slider"
4125 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
4126 </div>
4127 <div class="mxchat-threshold-hint">
4128 <?php esc_html_e('Lower values make the action trigger more easily. Higher values require more exact matches.', 'mxchat'); ?>
4129 </div>
4130 </div>
4131
4132 <div class="mxchat-modal-actions">
4133 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
4134 <?php esc_html_e('Cancel', 'mxchat'); ?>
4135 </button>
4136 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
4137 <?php esc_html_e('Save Action', 'mxchat'); ?>
4138 </button>
4139 </div>
4140 </div>
4141 </form>
4142 </div>
4143 </div>
4144
4145 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
4146 <div class="mxchat-action-loading-spinner"></div>
4147 <div class="mxchat-action-loading-text">
4148 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
4149 </div>
4150 </div>
4151 </div><!-- .mxchat-wrapper -->
4152 <?php
4153 }
4154
4155 /**
4156 * Helper method to trim phrases for display
4157 * Adding this in case it doesn't exist in your class
4158 */
4159 private function get_trimmed_phrases($phrases, $max_length = 100) {
4160 if (strlen($phrases) <= $max_length) {
4161 return $phrases;
4162 }
4163
4164 $trimmed = substr($phrases, 0, $max_length);
4165 $last_comma = strrpos($trimmed, ',');
4166
4167 if ($last_comma !== false) {
4168 $trimmed = substr($trimmed, 0, $last_comma);
4169 }
4170
4171 return $trimmed . '...';
4172 }
4173
4174 /**
4175 * AJAX handler for toggling an action on/off
4176 */
4177 public function mxchat_toggle_action() {
4178 // Check nonce
4179 if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'mxchat_actions_nonce')) {
4180 wp_send_json_error(array('message' => 'Security check failed'));
4181 return;
4182 }
4183
4184 // Check permissions
4185 if (!current_user_can('manage_options')) {
4186 wp_send_json_error(array('message' => 'Permission denied'));
4187 return;
4188 }
4189
4190 // Validate params
4191 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4192 $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false;
4193
4194 if (!$intent_id) {
4195 wp_send_json_error(array('message' => 'Invalid action ID'));
4196 return;
4197 }
4198
4199 // Update the intent/action status in the database
4200 global $wpdb;
4201 $table_name = $wpdb->prefix . 'mxchat_intents';
4202
4203 // Using the 'enabled' field - add this field if it doesn't exist
4204 $result = $wpdb->update(
4205 $table_name,
4206 array('enabled' => $enabled ? 1 : 0),
4207 array('id' => $intent_id),
4208 array('%d'),
4209 array('%d')
4210 );
4211
4212 if ($result === false) {
4213 wp_send_json_error(array('message' => 'Database error'));
4214 return;
4215 }
4216
4217 wp_send_json_success();
4218 }
4219
4220 /**
4221 * Add the 'enabled' column to the intents table if it doesn't exist
4222 * Call this during plugin activation or update
4223 */
4224 public function mxchat_add_enabled_column_to_intents() {
4225 global $wpdb;
4226 $table_name = $wpdb->prefix . 'mxchat_intents';
4227
4228 // Check if the column already exists
4229 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
4230
4231 if (empty($columns)) {
4232 // Add the column with default value of 1 (enabled)
4233 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
4234 }
4235 }
4236
4237 /**
4238 * Handle embedding generation errors using existing admin notice system
4239 *
4240 * @param string $message Error message to display
4241 * @param bool $redirect Whether to redirect back to the actions page
4242 * @return void
4243 */
4244 private function handle_embedding_error($message, $redirect = true) {
4245 // Store the error message in the existing transient
4246 set_transient('mxchat_admin_notice_error', $message, 60);
4247
4248 if ($redirect) {
4249 // Redirect back to the actions page
4250 $redirect_url = add_query_arg(
4251 array(
4252 'page' => 'mxchat-actions'
4253 ),
4254 admin_url('admin.php')
4255 );
4256 wp_safe_redirect($redirect_url);
4257 exit;
4258 }
4259 }
4260
4261 /**
4262 * Handle editing of intent phrases - with improved error handling
4263 *
4264 * @since 1.0.0
4265 * @return void
4266 */
4267 public function mxchat_handle_edit_intent() {
4268 // Security checks (nonce and permissions)
4269 if (!current_user_can('manage_options')) {
4270 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4271 }
4272 check_admin_referer('mxchat_edit_intent');
4273
4274 // Get POST data
4275 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
4276 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4277 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4278 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
4279 $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95
4280
4281 // Validate inputs
4282 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4283 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4284 return;
4285 }
4286
4287 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
4288 if (empty($phrases_array)) {
4289 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4290 return;
4291 }
4292
4293 // Generate embeddings with improved error handling
4294 $vectors = [];
4295 $failed_phrases = [];
4296
4297 foreach ($phrases_array as $phrase) {
4298 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
4299 if (is_array($embedding_vector)) {
4300 $vectors[] = $embedding_vector;
4301 } else {
4302 $failed_phrases[] = $phrase;
4303 }
4304 }
4305
4306 if (!empty($failed_phrases)) {
4307 $this->handle_embedding_error(
4308 sprintf(
4309 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4310 implode(', ', $failed_phrases)
4311 )
4312 );
4313 return;
4314 }
4315
4316 if (empty($vectors)) {
4317 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4318 return;
4319 }
4320
4321 $combined_vector = $this->mxchat_average_vectors($vectors);
4322 $serialized_vector = maybe_serialize($combined_vector);
4323
4324 // Update the database
4325 global $wpdb;
4326 $table_name = $wpdb->prefix . 'mxchat_intents';
4327
4328 $result = $wpdb->update(
4329 $table_name,
4330 array(
4331 'intent_label' => $intent_label,
4332 'phrases' => implode(', ', $phrases_array),
4333 'embedding_vector' => $serialized_vector,
4334 'similarity_threshold' => $similarity_threshold
4335 ),
4336 array('id' => $intent_id),
4337 array('%s', '%s', '%s', '%f'), // Format: string, string, string, float
4338 array('%d') // Where format: integer
4339 );
4340
4341 if (false === $result) {
4342 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
4343 return;
4344 }
4345
4346 // Set success message and redirect
4347 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
4348
4349 $redirect_url = add_query_arg(
4350 array(
4351 'page' => 'mxchat-actions'
4352 ),
4353 admin_url('admin.php')
4354 );
4355 wp_safe_redirect($redirect_url);
4356 exit;
4357 }
4358
4359 /**
4360 * Handle adding new intent - with improved error handling
4361 *
4362 * @return void
4363 */
4364 public function mxchat_handle_add_intent() {
4365 if (!current_user_can('manage_options')) {
4366 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4367 }
4368
4369 check_admin_referer('mxchat_add_intent_nonce');
4370
4371 global $wpdb;
4372 $table_name = $wpdb->prefix . 'mxchat_intents';
4373
4374 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4375 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4376 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4377 $default_threshold = 0.85;
4378
4379 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
4380 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
4381 return;
4382 }
4383
4384 $available_callbacks = $this->mxchat_get_available_callbacks();
4385
4386 if (!array_key_exists($callback_function, $available_callbacks)) {
4387 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
4388 return;
4389 }
4390
4391 $is_pro_only = $available_callbacks[$callback_function]['pro_only'];
4392 if ($is_pro_only && !$this->is_activated) {
4393 $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat'));
4394 return;
4395 }
4396
4397 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
4398
4399 if (empty($phrases_array)) {
4400 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
4401 return;
4402 }
4403
4404 // Generate embeddings with improved error handling
4405 $vectors = [];
4406 $failed_phrases = [];
4407
4408 foreach ($phrases_array as $phrase) {
4409 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
4410 if (is_array($embedding_vector)) {
4411 $vectors[] = $embedding_vector;
4412 } else {
4413 $failed_phrases[] = $phrase;
4414 }
4415 }
4416
4417 if (!empty($failed_phrases)) {
4418 $this->handle_embedding_error(
4419 sprintf(
4420 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
4421 implode(', ', $failed_phrases)
4422 )
4423 );
4424 return;
4425 }
4426
4427 if (empty($vectors)) {
4428 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
4429 return;
4430 }
4431
4432 $combined_vector = $this->mxchat_average_vectors($vectors);
4433 $serialized_vector = maybe_serialize($combined_vector);
4434
4435 $result = $wpdb->insert($table_name, [
4436 'intent_label' => $intent_label,
4437 'phrases' => implode(', ', $phrases_array),
4438 'embedding_vector' => $serialized_vector,
4439 'callback_function' => $callback_function,
4440 'similarity_threshold' => $default_threshold,
4441 ]);
4442
4443 if ($result === false) {
4444 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
4445 return;
4446 }
4447
4448 // Set success message
4449 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
4450
4451 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4452 exit;
4453 }
4454
4455
4456 /**
4457 * Update intent threshold with AJAX support
4458 */
4459 public function mxchat_update_intent_threshold() {
4460 // Check permissions
4461 if (!current_user_can('manage_options')) {
4462 if (wp_doing_ajax()) {
4463 wp_send_json_error(array('message' => 'Unauthorized user'));
4464 return;
4465 }
4466 wp_die(esc_html__('Unauthorized user', 'mxchat'));
4467 }
4468
4469 // Verify nonce
4470 check_admin_referer('mxchat_update_intent_threshold_nonce');
4471
4472 // Process the update if we have valid data
4473 if (isset($_POST['intent_id'], $_POST['intent_threshold'])) {
4474 global $wpdb;
4475 $table_name = $wpdb->prefix . 'mxchat_intents';
4476 $intent_id = intval($_POST['intent_id']);
4477 $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold'])));
4478 $similarity_threshold = $threshold_percentage / 100;
4479
4480 $result = $wpdb->update(
4481 $table_name,
4482 ['similarity_threshold' => $similarity_threshold],
4483 ['id' => $intent_id],
4484 ['%f'],
4485 ['%d']
4486 );
4487
4488 // Handle AJAX requests
4489 if (wp_doing_ajax()) {
4490 if ($result === false) {
4491 wp_send_json_error(array('message' => 'Failed to update threshold'));
4492 } else {
4493 wp_send_json_success(array('threshold' => $threshold_percentage));
4494 }
4495 return;
4496 }
4497 }
4498
4499 // Redirect for regular form submissions
4500 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true'));
4501 exit;
4502 }
4503
4504
4505
4506 /**
4507 * Enhanced get_available_callbacks function with form action exclusion
4508 *
4509 * @param bool $grouped Whether to return callbacks grouped by category
4510 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4511 * @return array Callbacks data with icons, descriptions and availability status
4512 */
4513 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4514 // Load WordPress plugin functions if needed
4515 if (!function_exists('get_plugins')) {
4516 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4517 }
4518
4519 // Get active plugins
4520 $active_plugins = get_option('active_plugins', array());
4521
4522 // Functions to exclude from the action selector
4523 $excluded_functions = array(
4524 'mxchat_handle_form_collection' // Exclude form actions
4525 );
4526
4527 // Define add-on plugin files and their corresponding action functions
4528 $addon_plugins = array(
4529 'mxchat-woo/mxchat-woo.php' => array(
4530 'functions' => array(
4531 'mxchat_handle_product_recommendations',
4532 'mxchat_handle_order_history',
4533 'mxchat_show_product_card',
4534 'mxchat_add_to_cart',
4535 'mxchat_checkout_redirect'
4536 ),
4537 'name' => __('WooCommerce Add-on', 'mxchat'),
4538 'pro_required' => true
4539 ),
4540 'mxchat-perplexity/mxchat-perplexity.php' => array(
4541 'functions' => array('mxchat_perplexity_research'),
4542 'name' => __('Perplexity Add-on', 'mxchat'),
4543 'pro_required' => true
4544 ),
4545 // Add other add-ons and their functions here
4546 );
4547
4548 // Get the functions that are provided by active add-ons
4549 $addon_provided_functions = array();
4550 $addon_function_mapping = array(); // Maps functions to their add-on info
4551
4552 // Check which add-ons are active
4553 foreach ($addon_plugins as $plugin_file => $addon_info) {
4554 $is_active = in_array($plugin_file, $active_plugins);
4555
4556 // For each function in this addon
4557 foreach ($addon_info['functions'] as $function) {
4558 // Consider a function installed only if:
4559 // 1. The add-on is active AND
4560 // 2. Either it doesn't require Pro OR Pro is activated
4561 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4562
4563 // If the add-on is installed, mark this function as provided by an add-on
4564 if ($is_installed) {
4565 $addon_provided_functions[] = $function;
4566 }
4567
4568 // Store addon info for this function regardless of installation status
4569 $addon_function_mapping[$function] = array(
4570 'addon' => basename(dirname($plugin_file)),
4571 'addon_name' => $addon_info['name'],
4572 'pro_required' => $addon_info['pro_required'],
4573 'is_active' => $is_active,
4574 'is_installed' => $is_installed
4575 );
4576 }
4577 }
4578
4579 // Core callbacks - always available in the base plugin
4580 $core_callbacks = array(
4581 'mxchat_handle_email_capture' => array(
4582 'label' => __('Loops Email Capture', 'mxchat'),
4583 'pro_only' => false,
4584 'group' => __('Customer Engagement', 'mxchat'),
4585 'icon' => 'email-alt',
4586 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4587 'addon' => false, // Not from an add-on
4588 'installed' => true // Always installed with base plugin
4589 ),
4590 'mxchat_handle_search_request' => array(
4591 'label' => __('Brave Web Search', 'mxchat'),
4592 'pro_only' => false,
4593 'group' => __('Search Features', 'mxchat'),
4594 'icon' => 'search',
4595 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4596 'addon' => false,
4597 'installed' => true
4598 ),
4599 'mxchat_handle_image_search_request' => array(
4600 'label' => __('Brave Image Search', 'mxchat'),
4601 'pro_only' => false,
4602 'group' => __('Search Features', 'mxchat'),
4603 'icon' => 'format-image',
4604 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4605 'addon' => false,
4606 'installed' => true
4607 ),
4608 // Pro core features - check is_activated property
4609 'mxchat_generate_image' => array(
4610 'label' => __('Generate Image', 'mxchat'),
4611 'pro_only' => false,
4612 'group' => __('Other Features', 'mxchat'),
4613 'icon' => 'art',
4614 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'),
4615 'addon' => false,
4616 'installed' => $this->is_activated
4617 ),
4618 'mxchat_handle_pdf_discussion' => array(
4619 'label' => __('Chat with PDF', 'mxchat'),
4620 'pro_only' => true,
4621 'group' => __('Other Features', 'mxchat'),
4622 'icon' => 'media-document',
4623 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4624 'addon' => false,
4625 'installed' => $this->is_activated
4626 ),
4627 'mxchat_live_agent_handover' => array(
4628 'label' => __('Slack Live Agent', 'mxchat'),
4629 'pro_only' => true,
4630 'group' => __('Customer Engagement', 'mxchat'),
4631 'icon' => 'admin-users',
4632 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4633 'addon' => false,
4634 'installed' => $this->is_activated
4635 ),
4636 'mxchat_handle_switch_to_chatbot_intent' => array(
4637 'label' => __('Back to Chatbot', 'mxchat'),
4638 'pro_only' => true,
4639 'group' => __('Customer Engagement', 'mxchat'),
4640 'icon' => 'backup',
4641 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4642 'addon' => false,
4643 'installed' => $this->is_activated
4644 ),
4645 );
4646
4647 // Add-on callbacks with placeholders - only include if the add-on is NOT active
4648 $addon_callbacks = array(
4649 // WooCommerce Add-on
4650 'mxchat_handle_product_recommendations' => array(
4651 'label' => __('Product Recommendations', 'mxchat'),
4652 'pro_only' => true,
4653 'group' => __('WooCommerce Features', 'mxchat'),
4654 'icon' => 'cart',
4655 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4656 ),
4657 'mxchat_handle_order_history' => array(
4658 'label' => __('Order History', 'mxchat'),
4659 'pro_only' => true,
4660 'group' => __('WooCommerce Features', 'mxchat'),
4661 'icon' => 'clipboard',
4662 'description' => __('Allow customers to check their order status', 'mxchat'),
4663 ),
4664 'mxchat_show_product_card' => array(
4665 'label' => __('Show Product Card', 'mxchat'),
4666 'pro_only' => true,
4667 'group' => __('WooCommerce Features', 'mxchat'),
4668 'icon' => 'products',
4669 'description' => __('Display product information in the chat', 'mxchat'),
4670 ),
4671 'mxchat_add_to_cart' => array(
4672 'label' => __('Add to Cart', 'mxchat'),
4673 'pro_only' => true,
4674 'group' => __('WooCommerce Features', 'mxchat'),
4675 'icon' => 'plus-alt',
4676 'description' => __('Add products to cart directly from chat', 'mxchat'),
4677 ),
4678 'mxchat_checkout_redirect' => array(
4679 'label' => __('Proceed to Checkout', 'mxchat'),
4680 'pro_only' => true,
4681 'group' => __('WooCommerce Features', 'mxchat'),
4682 'icon' => 'arrow-right-alt',
4683 'description' => __('Redirect customer to checkout page', 'mxchat'),
4684 ),
4685
4686 // Perplexity Add-on
4687 'mxchat_perplexity_research' => array(
4688 'label' => __('Perplexity Research', 'mxchat'),
4689 'pro_only' => true,
4690 'group' => __('Search Features', 'mxchat'),
4691 'icon' => 'book-alt',
4692 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4693 ),
4694 );
4695
4696 // Enhance add-on callbacks with installation status and addon info
4697 foreach ($addon_callbacks as $function => $data) {
4698 if (isset($addon_function_mapping[$function])) {
4699 $addon_info = $addon_function_mapping[$function];
4700
4701 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4702 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4703 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4704
4705 // Set pro_only based on add-on configuration
4706 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4707 } else {
4708 $addon_callbacks[$function]['addon'] = 'unknown';
4709 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4710 $addon_callbacks[$function]['installed'] = false;
4711 }
4712 }
4713
4714 // Initialize callbacks with core features
4715 $callbacks = $core_callbacks;
4716
4717 // Get callbacks from active add-ons
4718 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4719
4720 // Add placeholder callbacks only for add-ons that aren't active
4721 if ($include_all) {
4722 foreach ($addon_callbacks as $function => $data) {
4723 // Skip placeholders for functions provided by active add-ons
4724 if (in_array($function, $addon_provided_functions)) {
4725 continue;
4726 }
4727
4728 // Skip excluded functions
4729 if (in_array($function, $excluded_functions)) {
4730 continue;
4731 }
4732
4733 // Add the placeholder
4734 $callbacks[$function] = $data;
4735 }
4736 }
4737
4738 // Add callbacks from active add-ons (will override placeholders)
4739 foreach ($active_addon_callbacks as $function => $data) {
4740 // Skip excluded functions
4741 if (in_array($function, $excluded_functions)) {
4742 continue;
4743 }
4744
4745 // Always include callbacks from add-ons
4746 $callbacks[$function] = $data;
4747
4748 // Ensure they have the proper add-on info
4749 if (isset($addon_function_mapping[$function])) {
4750 $addon_info = $addon_function_mapping[$function];
4751 $callbacks[$function]['addon'] = $addon_info['addon'];
4752 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4753 $callbacks[$function]['installed'] = $addon_info['is_installed'];
4754 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4755 }
4756 }
4757
4758 // Just before returning callbacks, sort them to prioritize free features
4759 if (!$grouped) {
4760 // Create temporary arrays for sorting
4761 $free_callbacks = array();
4762 $pro_callbacks = array();
4763
4764 // Split callbacks into free and pro
4765 foreach ($callbacks as $key => $data) {
4766 if (isset($data['pro_only']) && $data['pro_only']) {
4767 $pro_callbacks[$key] = $data;
4768 } else {
4769 $free_callbacks[$key] = $data;
4770 }
4771 }
4772
4773 // Merge with free callbacks first
4774 $callbacks = array_merge($free_callbacks, $pro_callbacks);
4775 }
4776
4777 // Return grouped structure if requested
4778 if ($grouped) {
4779 $grouped_callbacks = array();
4780 foreach ($callbacks as $key => $data) {
4781 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4782
4783 // Ensure we carry forward all the new fields in grouped mode
4784 $callback_data = array(
4785 'label' => $data['label'],
4786 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4787 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4788 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4789 'addon' => isset($data['addon']) ? $data['addon'] : false,
4790 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4791 'installed' => isset($data['installed']) ? $data['installed'] : true
4792 );
4793
4794 $grouped_callbacks[$group_label][$key] = $callback_data;
4795 }
4796
4797 // Sort within each group to prioritize free features
4798 foreach ($grouped_callbacks as $group => $items) {
4799 $free_items = array();
4800 $pro_items = array();
4801
4802 foreach ($items as $key => $data) {
4803 if (isset($data['pro_only']) && $data['pro_only']) {
4804 $pro_items[$key] = $data;
4805 } else {
4806 $free_items[$key] = $data;
4807 }
4808 }
4809
4810 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4811 }
4812
4813 return $grouped_callbacks;
4814 }
4815
4816 return $callbacks;
4817 }
4818
4819
4820 private function mxchat_average_vectors($vectors) {
4821 $vector_length = count($vectors[0]);
4822 $sum_vector = array_fill(0, $vector_length, 0);
4823
4824 foreach ($vectors as $vector) {
4825 for ($i = 0; $i < $vector_length; $i++) {
4826 $sum_vector[$i] += $vector[$i];
4827 }
4828 }
4829
4830 // Divide each component by the number of vectors to get the average
4831 $num_vectors = count($vectors);
4832 for ($i = 0; $i < $vector_length; $i++) {
4833 $sum_vector[$i] /= $num_vectors;
4834 }
4835
4836 return $sum_vector;
4837 }
4838
4839 public function mxchat_handle_delete_intent() {
4840 if ( ! current_user_can( 'manage_options' ) ) {
4841 wp_die( esc_html__('Unauthorized user', 'mxchat') );
4842 }
4843
4844 check_admin_referer('mxchat_delete_intent_nonce');
4845
4846 if (isset($_POST['intent_id'])) {
4847 global $wpdb;
4848 $table_name = $wpdb->prefix . 'mxchat_intents';
4849 $intent_id = intval($_POST['intent_id']);
4850
4851 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
4852 }
4853
4854 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
4855 exit;
4856 }
4857
4858
4859 public function mxchat_page_init() {
4860 // Register settings
4861 register_setting(
4862 'mxchat_option_group',
4863 'mxchat_options',
4864 array($this, 'mxchat_sanitize')
4865 );
4866
4867 register_setting(
4868 'mxchat_option_group',
4869 'mxchat_similarity_threshold',
4870 array(
4871 'type' => 'number',
4872 'sanitize_callback' => function($value) {
4873 $value = absint($value); // Ensure it's an integer
4874 return min(max($value, 70), 85); // Enforce range
4875 },
4876 'default' => 80,
4877 )
4878 );
4879
4880 // Chatbot Settings Section
4881 add_settings_section(
4882 'mxchat_chatbot_section',
4883 esc_html__('Chatbot Settings', 'mxchat'),
4884 null,
4885 'mxchat-chatbot'
4886 );
4887
4888 // Similarity Threshold Slider
4889 add_settings_field(
4890 'similarity_threshold', // Field ID
4891 esc_html__('Similarity Threshold', 'mxchat'), // Field title
4892 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
4893 'mxchat-chatbot', // Page
4894 'mxchat_chatbot_section' // Section
4895 );
4896
4897 add_settings_field(
4898 'append_to_body',
4899 esc_html__('Auto-Display Chatbot', 'mxchat'),
4900 array($this, 'mxchat_append_to_body_callback'),
4901 'mxchat-chatbot',
4902 'mxchat_chatbot_section'
4903 );
4904
4905
4906 // Existing fields...
4907 add_settings_field(
4908 'api_key',
4909 esc_html__('OpenAI API Key', 'mxchat'),
4910 array($this, 'api_key_callback'),
4911 'mxchat-chatbot',
4912 'mxchat_chatbot_section',
4913 array(
4914 'class' => 'mxchat-setting-row',
4915 'data-provider' => 'openai'
4916 )
4917 );
4918
4919 add_settings_field(
4920 'xai_api_key',
4921 esc_html__('X.AI API Key', 'mxchat'),
4922 array($this, 'xai_api_key_callback'),
4923 'mxchat-chatbot',
4924 'mxchat_chatbot_section',
4925 array(
4926 'class' => 'mxchat-setting-row',
4927 'data-provider' => 'xai'
4928 )
4929 );
4930
4931 add_settings_field(
4932 'claude_api_key',
4933 esc_html__('Claude API Key', 'mxchat'),
4934 array($this, 'claude_api_key_callback'),
4935 'mxchat-chatbot',
4936 'mxchat_chatbot_section',
4937 array(
4938 'class' => 'mxchat-setting-row',
4939 'data-provider' => 'claude'
4940 )
4941 );
4942
4943 add_settings_field(
4944 'deepseek_api_key',
4945 esc_html__('DeepSeek API Key', 'mxchat'),
4946 array($this, 'deepseek_api_key_callback'),
4947 'mxchat-chatbot',
4948 'mxchat_chatbot_section',
4949 array(
4950 'class' => 'mxchat-setting-row',
4951 'data-provider' => 'deepseek'
4952 )
4953 );
4954
4955 add_settings_field(
4956 'gemini_api_key',
4957 esc_html__('Google Gemini API Key', 'mxchat'),
4958 array($this, 'gemini_api_key_callback'),
4959 'mxchat-chatbot',
4960 'mxchat_chatbot_section',
4961 array(
4962 'class' => 'mxchat-setting-row',
4963 'data-provider' => 'gemini'
4964 )
4965 );
4966
4967 add_settings_field(
4968 'voyage_api_key',
4969 esc_html__('Voyage AI API Key', 'mxchat'),
4970 array($this, 'voyage_api_key_callback'),
4971 'mxchat-chatbot',
4972 'mxchat_chatbot_section',
4973 array(
4974 'class' => 'mxchat-setting-row',
4975 'data-provider' => 'voyage'
4976 )
4977 );
4978
4979 add_settings_field(
4980 'model',
4981 esc_html__('Chat Model', 'mxchat'),
4982 array($this, 'mxchat_model_callback'),
4983 'mxchat-chatbot',
4984 'mxchat_chatbot_section'
4985 );
4986
4987 // Add the settings field
4988 add_settings_field(
4989 'embedding_model',
4990 esc_html__('Embedding Model', 'mxchat'),
4991 array($this, 'embedding_model_callback'),
4992 'mxchat-chatbot',
4993 'mxchat_chatbot_section'
4994 );
4995
4996 add_settings_field(
4997 'system_prompt_instructions',
4998 esc_html__('AI Instructions (Behavior)', 'mxchat'),
4999 array($this, 'system_prompt_instructions_callback'),
5000 'mxchat-chatbot',
5001 'mxchat_chatbot_section'
5002 );
5003
5004
5005 add_settings_field(
5006 'top_bar_title',
5007 esc_html__('Top Bar Title', 'mxchat'),
5008 array($this, 'mxchat_top_bar_title_callback'),
5009 'mxchat-chatbot',
5010 'mxchat_chatbot_section'
5011 );
5012
5013 add_settings_field(
5014 'ai_agent_text',
5015 esc_html__('AI Agent Text', 'mxchat'),
5016 array($this, 'mxchat_ai_agent_text_callback'),
5017 'mxchat-chatbot',
5018 'mxchat_chatbot_section'
5019 );
5020
5021 add_settings_field(
5022 'enable_email_block',
5023 esc_html__('Require Email To Chat', 'mxchat'),
5024 array($this, 'enable_email_block_callback'),
5025 'mxchat-chatbot',
5026 'mxchat_chatbot_section'
5027 );
5028
5029 add_settings_field(
5030 'email_blocker_header_content',
5031 esc_html__('Require Email Chat Content', 'mxchat'),
5032 array($this, 'email_blocker_header_content_callback'),
5033 'mxchat-chatbot',
5034 'mxchat_chatbot_section'
5035 );
5036
5037 add_settings_field(
5038 'email_blocker_button_text',
5039 esc_html__('Require Email Chat Button Text', 'mxchat'),
5040 [$this, 'email_blocker_button_text_callback'],
5041 'mxchat-chatbot',
5042 'mxchat_chatbot_section'
5043 );
5044
5045 add_settings_field(
5046 'intro_message',
5047 esc_html__('Introductory Message', 'mxchat'),
5048 array($this, 'mxchat_intro_message_callback'),
5049 'mxchat-chatbot',
5050 'mxchat_chatbot_section'
5051 );
5052
5053 add_settings_field(
5054 'input_copy',
5055 esc_html__('Input Copy', 'mxchat'),
5056 array($this, 'mxchat_input_copy_callback'),
5057 'mxchat-chatbot',
5058 'mxchat_chatbot_section'
5059 );
5060
5061 add_settings_field(
5062 'rate_limit_logged_out',
5063 __('Rate Limit for Logged-out Users', 'mxchat'),
5064 array($this, 'mxchat_rate_limit_logged_out_callback'),
5065 'mxchat-chatbot',
5066 'mxchat_chatbot_section'
5067 );
5068
5069 add_settings_field(
5070 'rate_limit_roles',
5071 __('Rate Limits by User Role', 'mxchat'),
5072 array($this, 'mxchat_rate_limit_roles_callback'),
5073 'mxchat-chatbot',
5074 'mxchat_chatbot_section'
5075 );
5076
5077 add_settings_field(
5078 'rate_limit_message',
5079 esc_html__('Rate Limit Message', 'mxchat'),
5080 array($this, 'mxchat_rate_limit_message_callback'),
5081 'mxchat-chatbot',
5082 'mxchat_chatbot_section'
5083 );
5084
5085 add_settings_field(
5086 'pre_chat_message',
5087 esc_html__('Chat Teaser Pop-up', 'mxchat'),
5088 array($this, 'mxchat_pre_chat_message_callback'),
5089 'mxchat-chatbot',
5090 'mxchat_chatbot_section'
5091 );
5092
5093 add_settings_field(
5094 'privacy_toggle',
5095 esc_html__('Toggle Privacy Notice', 'mxchat'),
5096 array($this, 'mxchat_privacy_toggle_callback'),
5097 'mxchat-chatbot',
5098 'mxchat_chatbot_section'
5099 );
5100
5101 add_settings_field(
5102 'complianz_toggle',
5103 esc_html__('Enable Complianz', 'mxchat'),
5104 array($this, 'mxchat_complianz_toggle_callback'),
5105 'mxchat-chatbot',
5106 'mxchat_chatbot_section'
5107 );
5108
5109 add_settings_field(
5110 'link_target_toggle',
5111 esc_html__('Open Links in a New Tab', 'mxchat'),
5112 array($this, 'mxchat_link_target_toggle_callback'),
5113 'mxchat-chatbot',
5114 'mxchat_chatbot_section'
5115 );
5116
5117 add_settings_field(
5118 'chat_persistence_toggle',
5119 esc_html__('Enable Chat Persistence', 'mxchat'),
5120 array($this, 'mxchat_chat_persistence_toggle_callback'),
5121 'mxchat-chatbot',
5122 'mxchat_chatbot_section'
5123 );
5124
5125 add_settings_field(
5126 'popular_question_1',
5127 esc_html__('Quick Question 1', 'mxchat'),
5128 array($this, 'mxchat_popular_question_1_callback'),
5129 'mxchat-chatbot',
5130 'mxchat_chatbot_section'
5131 );
5132
5133 add_settings_field(
5134 'popular_question_2',
5135 esc_html__('Quick Question 2', 'mxchat'),
5136 array($this, 'mxchat_popular_question_2_callback'),
5137 'mxchat-chatbot',
5138 'mxchat_chatbot_section'
5139 );
5140
5141 add_settings_field(
5142 'popular_question_3',
5143 esc_html__('Quick Question 3', 'mxchat'),
5144 array($this, 'mxchat_popular_question_3_callback'),
5145 'mxchat-chatbot',
5146 'mxchat_chatbot_section'
5147 );
5148
5149 add_settings_field(
5150 'additional_popular_questions',
5151 esc_html__('Additional Quick Questions', 'mxchat'),
5152 array($this, 'mxchat_additional_popular_questions_callback'),
5153 'mxchat-chatbot',
5154 'mxchat_chatbot_section'
5155 );
5156
5157
5158 // Loops Settings Section
5159 add_settings_section(
5160 'mxchat_loops_section',
5161 esc_html__('Loops Settings', 'mxchat'),
5162 null,
5163 'mxchat-embed'
5164 );
5165
5166 // Loops Settings Fields
5167 add_settings_field(
5168 'loops_api_key',
5169 esc_html__('Loops API Key', 'mxchat'),
5170 array($this, 'mxchat_loops_api_key_callback'),
5171 'mxchat-embed',
5172 'mxchat_loops_section'
5173 );
5174
5175 add_settings_field(
5176 'loops_mailing_list',
5177 esc_html__('Loops Mailing List', 'mxchat'),
5178 array($this, 'mxchat_loops_mailing_list_callback'),
5179 'mxchat-embed',
5180 'mxchat_loops_section'
5181 );
5182
5183 add_settings_field(
5184 'triggered_phrase_response',
5185 esc_html__('Triggered Phrase Response', 'mxchat'),
5186 array($this, 'mxchat_triggered_phrase_response_callback'),
5187 'mxchat-embed',
5188 'mxchat_loops_section'
5189 );
5190
5191 add_settings_field(
5192 'email_capture_response',
5193 esc_html__('Email Capture Response', 'mxchat'),
5194 array($this, 'mxchat_email_capture_response_callback'),
5195 'mxchat-embed',
5196 'mxchat_loops_section'
5197 );
5198
5199 // Brave Search Settings Fields
5200 add_settings_section(
5201 'mxchat_brave_section',
5202 __('Brave Search Settings', 'mxchat'),
5203 array($this, 'mxchat_brave_section_callback'),
5204 'mxchat-embed'
5205 );
5206
5207 add_settings_field(
5208 'brave_api_key',
5209 __('Brave API Key', 'mxchat'),
5210 array($this, 'mxchat_brave_api_key_callback'),
5211 'mxchat-embed',
5212 'mxchat_brave_section'
5213 );
5214
5215 add_settings_field(
5216 'brave_image_count',
5217 __('Number of Images to Return', 'mxchat'),
5218 array($this, 'mxchat_brave_image_count_callback'),
5219 'mxchat-embed',
5220 'mxchat_brave_section'
5221 );
5222
5223 add_settings_field(
5224 'brave_safe_search',
5225 __('Safe Search', 'mxchat'),
5226 array($this, 'mxchat_brave_safe_search_callback'),
5227 'mxchat-embed',
5228 'mxchat_brave_section'
5229 );
5230
5231 add_settings_field(
5232 'brave_news_count',
5233 __('Number of News Articles', 'mxchat'),
5234 array($this, 'mxchat_brave_news_count_callback'),
5235 'mxchat-embed',
5236 'mxchat_brave_section'
5237 );
5238
5239 add_settings_field(
5240 'brave_country',
5241 __('Country', 'mxchat'),
5242 array($this, 'mxchat_brave_country_callback'),
5243 'mxchat-embed',
5244 'mxchat_brave_section'
5245 );
5246
5247 add_settings_field(
5248 'brave_language',
5249 __('Language', 'mxchat'),
5250 array($this, 'mxchat_brave_language_callback'),
5251 'mxchat-embed',
5252 'mxchat_brave_section'
5253 );
5254
5255 // Chat with PDF Intent Settings Fields
5256 add_settings_section(
5257 'mxchat_pdf_intent_section',
5258 __('Toolbar Settings & Intents', 'mxchat'),
5259 array($this, 'mxchat_pdf_intent_section_callback'),
5260 'mxchat-embed'
5261 );
5262
5263 add_settings_field(
5264 'chat_toolbar_toggle',
5265 __('Show Chat Toolbar', 'mxchat'),
5266 array($this, 'mxchat_chat_toolbar_toggle_callback'),
5267 'mxchat-embed',
5268 'mxchat_pdf_intent_section'
5269 );
5270
5271 // PDF Upload Button Toggle
5272 add_settings_field(
5273 'show_pdf_upload_button',
5274 __('Show PDF Upload Button', 'mxchat'),
5275 array($this, 'mxchat_show_pdf_upload_button_callback'),
5276 'mxchat-embed',
5277 'mxchat_pdf_intent_section'
5278 );
5279
5280 // Word Upload Button Toggle
5281 add_settings_field(
5282 'show_word_upload_button',
5283 __('Show Word Upload Button', 'mxchat'),
5284 array($this, 'mxchat_show_word_upload_button_callback'),
5285 'mxchat-embed',
5286 'mxchat_pdf_intent_section'
5287 );
5288
5289 add_settings_field(
5290 'pdf_intent_trigger_text',
5291 __('Intent Trigger Text', 'mxchat'),
5292 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
5293 'mxchat-embed',
5294 'mxchat_pdf_intent_section'
5295 );
5296
5297 add_settings_field(
5298 'pdf_intent_success_text',
5299 __('Success Text', 'mxchat'),
5300 array($this, 'mxchat_pdf_intent_success_text_callback'),
5301 'mxchat-embed',
5302 'mxchat_pdf_intent_section'
5303 );
5304
5305 add_settings_field(
5306 'pdf_intent_error_text',
5307 __('Error Text', 'mxchat'),
5308 array($this, 'mxchat_pdf_intent_error_text_callback'),
5309 'mxchat-embed',
5310 'mxchat_pdf_intent_section'
5311 );
5312
5313 // Add PDF Maximum Pages Field
5314 add_settings_field(
5315 'pdf_max_pages',
5316 __('Maximum Document Pages', 'mxchat'),
5317 array($this, 'mxchat_pdf_max_pages_callback'),
5318 'mxchat-embed',
5319 'mxchat_pdf_intent_section'
5320 );
5321
5322 // Live Agent Settings Fields
5323 add_settings_section(
5324 'mxchat_live_agent_section',
5325 __('Live Agent Settings', 'mxchat'),
5326 array($this, 'mxchat_live_agent_section_callback'),
5327 'mxchat-embed'
5328 );
5329
5330 // Live Agent Status Fields (add at top of live agent settings)
5331 add_settings_field(
5332 'live_agent_status',
5333 __('Live Agent Status', 'mxchat'),
5334 array($this, 'mxchat_live_agent_status_callback'),
5335 'mxchat-embed',
5336 'mxchat_live_agent_section'
5337 );
5338
5339 add_settings_field(
5340 'live_agent_notification_message',
5341 __('Notification Message', 'mxchat'),
5342 array($this, 'mxchat_live_agent_notification_message_callback'),
5343 'mxchat-embed',
5344 'mxchat_live_agent_section'
5345 );
5346
5347 add_settings_field(
5348 'live_agent_away_message',
5349 __('Away Message', 'mxchat'),
5350 array($this, 'mxchat_live_agent_away_message_callback'),
5351 'mxchat-embed',
5352 'mxchat_live_agent_section'
5353 );
5354
5355 add_settings_field(
5356 'live_agent_webhook_url',
5357 __('Slack Webhook URL', 'mxchat'),
5358 array($this, 'mxchat_live_agent_webhook_url_callback'),
5359 'mxchat-embed',
5360 'mxchat_live_agent_section'
5361 );
5362
5363 add_settings_field(
5364 'live_agent_secret_key',
5365 __('Slack Secret Key', 'mxchat'),
5366 array($this, 'mxchat_live_agent_secret_key_callback'),
5367 'mxchat-embed',
5368 'mxchat_live_agent_section'
5369 );
5370
5371 // Live Agent Integration Fields
5372 add_settings_field(
5373 'live_agent_bot_token',
5374 __('Slack Bot OAuth Token', 'mxchat'),
5375 array($this, 'mxchat_live_agent_bot_token_callback'),
5376 'mxchat-embed',
5377 'mxchat_live_agent_section'
5378 );
5379
5380
5381
5382
5383 // General Settings Section
5384 add_settings_section(
5385 'mxchat_general_section',
5386 esc_html__('YouTube Tutorials', 'mxchat'),
5387 null,
5388 'mxchat-general'
5389 );
5390 }
5391
5392 public function mxchat_prompts_page_init() {
5393 // Register all settings as a single array
5394 register_setting(
5395 'mxchat_prompts_options',
5396 'mxchat_prompts_options',
5397 array(
5398 'type' => 'array',
5399 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
5400 'default' => array(
5401 'mxchat_auto_sync_posts' => 0,
5402 'mxchat_auto_sync_pages' => 0,
5403 'mxchat_use_pinecone' => 0,
5404 'mxchat_pinecone_api_key' => '',
5405 'mxchat_pinecone_environment' => '',
5406 'mxchat_pinecone_index' => '',
5407 'mxchat_pinecone_host' => '', // Add this line
5408 ),
5409 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
5410 )
5411 );
5412
5413 // Add settings saved message
5414 add_action('admin_notices', array($this, 'sync_settings_notice'));
5415 }
5416
5417 /**
5418 * Sanitize all prompts options
5419 *
5420 * @param array $input The unsanitized options array
5421 * @return array The sanitized options array
5422 */
5423 public function sanitize_prompts_options($input) {
5424 // Log the incoming input.
5425 //error_log('Sanitizing inputs: ' . print_r($input, true));
5426
5427 $sanitized = array();
5428
5429 // Boolean options
5430 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
5431 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
5432 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
5433
5434 // API Key: if less than 32 characters, flag as invalid.
5435 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
5436 if (!empty($api_key) && strlen($api_key) < 32) {
5437 add_settings_error(
5438 'mxchat_prompts_options',
5439 'invalid_api_key',
5440 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
5441 );
5442 $existing_options = get_option('mxchat_prompts_options', array());
5443 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
5444 } else {
5445 $sanitized['mxchat_pinecone_api_key'] = $api_key;
5446 }
5447
5448 // Environment and Index Name
5449 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
5450 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
5451
5452 // Host: Remove protocol and validate format.
5453 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
5454 $host = preg_replace('#^https?://#', '', $host);
5455 //error_log('Host after removing protocol: ' . $host);
5456 if (!empty($host)) {
5457 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
5458 add_settings_error(
5459 'mxchat_prompts_options',
5460 'invalid_host',
5461 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
5462 );
5463 $existing_options = get_option('mxchat_prompts_options', array());
5464 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
5465 } else {
5466 $sanitized['mxchat_pinecone_host'] = $host;
5467 }
5468 } else {
5469 $sanitized['mxchat_pinecone_host'] = '';
5470 }
5471
5472 //error_log('Final sanitized array: ' . print_r($sanitized, true));
5473
5474 return $sanitized;
5475 }
5476
5477
5478 public function sync_settings_notice() {
5479 // Only show notice on our plugin page
5480 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
5481 return;
5482 }
5483
5484 // Check if settings were updated
5485 if (isset($_GET['settings-updated'])) {
5486
5487 ?>
5488 <div class="notice notice-success is-dismissible">
5489 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
5490 </div>
5491 <?php
5492
5493 }
5494 }
5495 // Add this sanitization function to your class
5496 public function sanitize_sync_setting($input) {
5497 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
5498 }
5499
5500
5501
5502 public function mxchat_handle_activate_license() {
5503 // Check nonce
5504 if (!check_ajax_referer('mxchat_activate_license_nonce', 'security', false)) {
5505 wp_send_json_error(esc_html__('Invalid security token', 'mxchat'));
5506 return;
5507 }
5508
5509 // Verify user capabilities
5510 if (!current_user_can('manage_options')) {
5511 wp_send_json_error(esc_html__('Unauthorized access', 'mxchat'));
5512 return;
5513 }
5514
5515 $license_key = isset($_POST['mxchat_activation_key']) ? sanitize_text_field($_POST['mxchat_activation_key']) : '';
5516 $customer_email = isset($_POST['mxchat_pro_email']) ? sanitize_email($_POST['mxchat_pro_email']) : '';
5517
5518 if (empty($license_key) || empty($customer_email)) {
5519 wp_send_json_error(esc_html__('Email or License Key is missing', 'mxchat'));
5520 return;
5521 }
5522
5523 $product_id = 'MxChatPRO';
5524 $response = wp_remote_get(
5525 add_query_arg(
5526 array(
5527 'wc-api' => 'software-api',
5528 'request' => 'activation',
5529 'email' => $customer_email,
5530 'license_key' => $license_key,
5531 'product_id' => $product_id
5532 ),
5533 'http://mxchat.ai/'
5534 )
5535 );
5536
5537 if (is_wp_error($response)) {
5538 wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message());
5539 return;
5540 }
5541
5542 $body = wp_remote_retrieve_body($response);
5543 $data = json_decode($body);
5544
5545 if ($data && isset($data->activated) && $data->activated) {
5546 update_option('mxchat_license_status', 'active');
5547 update_option('mxchat_pro_email', $customer_email);
5548 update_option('mxchat_activation_key', $license_key);
5549 wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat')));
5550 } else {
5551 $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat');
5552 update_option('mxchat_license_status', 'inactive');
5553 update_option('mxchat_license_error', $error_message);
5554 wp_send_json_error($error_message);
5555 }
5556 }
5557
5558
5559 public function mxchat_rate_limit_logged_out_callback() {
5560 // Load the entire 'mxchat_options' array
5561 $all_options = get_option('mxchat_options', []);
5562
5563 // Retrieve the saved rate limit or use the default value
5564 $default_rate_limit = '10';
5565 $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit;
5566
5567 // Define available rate limits
5568 $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5569
5570 // Output the dropdown
5571 echo '<div class="pro-feature-wrapper active">';
5572 echo '<select id="rate_limit_logged_out" name="rate_limit_logged_out">';
5573 foreach ($rate_limits as $limit) {
5574 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5575 }
5576 echo '</select>';
5577 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>';
5578 echo '</div>';
5579 }
5580
5581 // Add this new callback function
5582 public function mxchat_rate_limit_roles_callback() {
5583 $all_options = get_option('mxchat_options', []);
5584 $roles = wp_roles()->get_names();
5585 $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
5586
5587 echo '<div class="pro-feature-wrapper active mxchat-autosave-section">';
5588
5589 foreach ($roles as $role_id => $role_name) {
5590 $default_rate_limit = '100';
5591 $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id])
5592 ? $all_options['role_rate_limits'][$role_id]
5593 : $default_rate_limit;
5594
5595 echo '<div style="margin-bottom: 10px;">';
5596 echo '<label style="display: inline-block; width: 150px;">' . esc_html($role_name) . ':</label>';
5597 echo '<select
5598 id="role_rate_limits_' . esc_attr($role_id) . '"
5599 name="mxchat_options[role_rate_limits][' . esc_attr($role_id) . ']"
5600 class="mxchat-autosave-field">';
5601 foreach ($rate_limits as $limit) {
5602 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_rate_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5603 }
5604 echo '</select>';
5605 echo '</div>';
5606 }
5607
5608 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>';
5609 echo '</div>';
5610 }
5611
5612 public function mxchat_rate_limit_message_callback() {
5613 // Load the entire 'mxchat_options' array
5614 $all_options = get_option('mxchat_options', []);
5615
5616 // Retrieve the saved message or use the default value
5617 $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat');
5618 $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message;
5619
5620 // Output the textarea
5621 echo '<div class="pro-feature-wrapper active">';
5622 printf(
5623 '<textarea id="rate_limit_message" name="rate_limit_message" rows="3" cols="50">%s</textarea>',
5624 esc_textarea($rate_limit_message)
5625 );
5626 echo '<p class="description">' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '</p>';
5627 echo '</div>';
5628 }
5629
5630
5631 private function mxchat_add_option_field($id, $title, $callback = '') {
5632 add_settings_field(
5633 $id,
5634 __($title, 'mxchat'),
5635 $callback ? array($this, $callback) : array($this, $id . '_callback'),
5636 'mxchat-max',
5637 'mxchat_setting_section_id',
5638 $id === 'model' ? ['label_for' => 'model'] : []
5639 );
5640 }
5641
5642 // OpenAI API Key
5643 public function api_key_callback() {
5644 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5645
5646 echo '<div class="api-key-wrapper" data-provider="openai">';
5647 echo '<input type="password" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5648 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5649 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5650 echo '</div>';
5651 }
5652
5653 // X.AI API Key
5654 public function xai_api_key_callback() {
5655 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5656
5657 echo '<div class="api-key-wrapper" data-provider="xai">';
5658 echo '<input type="password" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text" autocomplete="off" />';
5659 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5660 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5661 echo '</div>';
5662 }
5663 // Claude API Key
5664 public function claude_api_key_callback() {
5665 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5666
5667 echo '<div class="api-key-wrapper" data-provider="claude">';
5668 echo '<input type="password" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text" autocomplete="off" />';
5669 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5670 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5671 echo '</div>';
5672 }
5673
5674 // DeepSeek API Key
5675 public function deepseek_api_key_callback() {
5676 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5677
5678 echo '<div class="api-key-wrapper" data-provider="deepseek">';
5679 echo '<input type="password" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5680 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5681 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '</p>';
5682 echo '</div>';
5683 }
5684
5685 // Gemini API Key
5686 public function gemini_api_key_callback() {
5687 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5688
5689 echo '<div class="api-key-wrapper" data-provider="gemini">';
5690 echo '<input type="password" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text" autocomplete="off" />';
5691 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5692 echo '<p class="description api-key-notice">' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5693 echo '</div>';
5694 }
5695
5696 // Voyage API Key
5697 public function voyage_api_key_callback() {
5698 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5699
5700 echo '<div class="api-key-wrapper" data-provider="voyage">';
5701 echo '<input type="password" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text" autocomplete="off" />';
5702 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5703 echo '<p class="description api-key-notice">' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '</p>';
5704 echo '</div>';
5705 }
5706
5707 public function mxchat_loops_api_key_callback() {
5708 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5709
5710 // Hidden fields to "trap" autofill
5711 echo '<input type="text" style="display:none" autocomplete="username" />';
5712 echo '<input type="password" style="display:none" autocomplete="current-password" />';
5713
5714 echo '<div class="api-key-wrapper" data-provider="loops">';
5715 echo sprintf(
5716 '<input type="password" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text" autocomplete="new-password" />',
5717 $loops_api_key
5718 );
5719 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5720 echo '</div>';
5721 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>';
5722 }
5723
5724
5725 public function mxchat_loops_mailing_list_callback() {
5726 // Add error handling and type checking
5727 $loops_api_key = '';
5728 $selected_list = '';
5729
5730 // Safely get the API key
5731 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
5732 $loops_api_key = $this->options['loops_api_key'];
5733 }
5734
5735 // Safely get the selected list
5736 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
5737 $selected_list = $this->options['loops_mailing_list'];
5738 }
5739
5740 if (!empty($loops_api_key)) {
5741 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
5742 if (is_array($lists) && !empty($lists)) {
5743 echo '<select id="loops_mailing_list" name="loops_mailing_list">';
5744 foreach ($lists as $list) {
5745 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
5746 echo sprintf(
5747 '<option value="%s" %s>%s</option>',
5748 esc_attr($list['id']),
5749 selected($selected_list, $list['id'], false),
5750 esc_html($list['name'])
5751 );
5752 }
5753 }
5754 echo '</select>';
5755 } else {
5756 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
5757 }
5758 } else {
5759 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
5760 }
5761 }
5762 public function mxchat_triggered_phrase_response_callback() {
5763 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
5764 $triggered_response = isset($this->options['triggered_phrase_response'])
5765 ? $this->options['triggered_phrase_response']
5766 : $default_response;
5767
5768 echo sprintf(
5769 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50">%s</textarea>',
5770 esc_textarea($triggered_response)
5771 );
5772 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>';
5773 }
5774
5775 public function mxchat_email_capture_response_callback() {
5776 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
5777 $email_capture_response = isset($this->options['email_capture_response'])
5778 ? $this->options['email_capture_response']
5779 : $default_response;
5780
5781 echo sprintf(
5782 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50">%s</textarea>',
5783 esc_textarea($email_capture_response)
5784 );
5785 echo '<p class="description">' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '</p>';
5786 }
5787
5788 public function mxchat_pre_chat_message_callback() {
5789 // Load the entire 'mxchat_options' array
5790 $all_options = get_option('mxchat_options', []);
5791
5792 // Retrieve the saved message or use the default value
5793 $default_message = __('Hey there! Ask me anything!', 'mxchat');
5794 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
5795
5796 // Output the textarea
5797 printf(
5798 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
5799 esc_textarea($pre_chat_message)
5800 );
5801 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>';
5802 }
5803
5804
5805
5806 // Callback for AI Instructions textarea
5807 public function system_prompt_instructions_callback() {
5808 // Retrieve the current value of the system prompt instructions
5809 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
5810
5811 // Render the textarea field
5812 printf(
5813 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
5814 $instructions
5815 );
5816
5817 // Provide a helpful description
5818 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>';
5819 }
5820
5821
5822 public function mxchat_model_callback() {
5823 // Define available models grouped by provider
5824 $models = array(
5825 esc_html__('Google Gemini Models', 'mxchat') => array(
5826 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'),
5827 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'),
5828 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'),
5829 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'),
5830 ),
5831 esc_html__('X.AI Models', 'mxchat') => array(
5832 'grok-beta' => esc_html__('grok-beta (Early Beta)', '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-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'),
5847 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'),
5848 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'),
5849 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'),
5850 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat')
5851 )
5852 );
5853
5854 // Retrieve the currently selected model from saved options
5855 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o';
5856
5857 // Begin the select dropdown
5858 echo '<select id="model" name="model">';
5859
5860 // Iterate over groups of models
5861 foreach ($models as $group_label => $group_models) {
5862 echo '<optgroup label="' . esc_attr($group_label) . '">';
5863
5864 foreach ($group_models as $model_value => $model_label) {
5865 // All models enabled - no disabled attribute or Pro Only label
5866 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5867 }
5868
5869 echo '</optgroup>';
5870 }
5871
5872 // Close the select dropdown
5873 echo '</select>';
5874
5875 // Updated description to remove mention of Pro-only models
5876 echo '<p class="description">' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '</p>';
5877 }
5878
5879
5880 // Callback function for embedding model selection
5881 public function embedding_model_callback() {
5882 $models = array(
5883 esc_html__('OpenAI Embeddings', 'mxchat') => array(
5884 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5885 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5886 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5887 ),
5888 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5889 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5890 )
5891 );
5892
5893 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5894
5895 echo '<select id="embedding_model" name="embedding_model">';
5896 foreach ($models as $group_label => $group_models) {
5897 echo '<optgroup label="' . esc_attr($group_label) . '">';
5898 foreach ($group_models as $model_value => $model_label) {
5899 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5900 }
5901 echo '</optgroup>';
5902 }
5903 echo '</select>';
5904 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>';
5905
5906 }
5907
5908
5909 public function mxchat_top_bar_title_callback() {
5910 // Retrieve the current value of the top bar title from saved options
5911 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
5912
5913 // Render the input field
5914 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
5915
5916 // Add a description
5917 echo '<p class="description">' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '</p>';
5918 }
5919 public function mxchat_ai_agent_text_callback() {
5920 // Retrieve the current value of the AI agent text from saved options
5921 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
5922 // Render the input field
5923 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
5924 // Add a description
5925 echo '<p class="description">' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '</p>';
5926 }
5927 public function enable_email_block_callback() {
5928 // Load full plugin options array
5929 $all_options = get_option('mxchat_options', []);
5930
5931 // Get the value, default to 'off'
5932 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
5933
5934 // Check if it's 'on'
5935 $checked = ($enable_email_block === 'on') ? 'checked' : '';
5936
5937 echo '<label class="toggle-switch">';
5938 echo sprintf(
5939 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
5940 esc_attr($checked)
5941 );
5942 echo '<span class="slider"></span>';
5943 echo '</label>';
5944 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>';
5945 }
5946
5947
5948 public function email_blocker_header_content_callback() {
5949 // Load the entire 'mxchat_options' array
5950 $all_options = get_option('mxchat_options', []);
5951
5952 // Retrieve the saved content or default to empty
5953 $content = isset($all_options['email_blocker_header_content'])
5954 ? $all_options['email_blocker_header_content']
5955 : '';
5956
5957 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
5958 echo '<textarea
5959 id="email_blocker_header_content"
5960 name="email_blocker_header_content"
5961 rows="5"
5962 cols="70"
5963 data-setting="email_blocker_header_content"
5964 >' . esc_textarea($content) . '</textarea>';
5965
5966 echo '<p class="description">';
5967 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');
5968 echo '</p>';
5969 }
5970
5971 public function email_blocker_button_text_callback() {
5972 // Load the entire 'mxchat_options' array
5973 $all_options = get_option('mxchat_options', []);
5974
5975 // Retrieve the saved button text or default to empty
5976 $button_text = isset($all_options['email_blocker_button_text'])
5977 ? $all_options['email_blocker_button_text']
5978 : '';
5979
5980 // Use esc_attr to safely render the existing text
5981 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
5982
5983 echo '<p class="description">';
5984 echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat');
5985 echo '</p>';
5986 }
5987
5988
5989
5990 public function mxchat_intro_message_callback() {
5991 // Load the entire 'mxchat_options' array
5992 $all_options = get_option('mxchat_options', []);
5993 // Retrieve the saved intro message or use the default
5994 $default_message = __('Hello! How can I assist you today?', 'mxchat');
5995 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
5996 // Output the textarea with the saved value without escaping HTML
5997 ?>
5998 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
5999 <p class="description">
6000 <?php esc_html_e('Enter your message. HTML tags and line breaks will be preserved.', 'mxchat'); ?>
6001 </p>
6002 <?php
6003 }
6004
6005 public function mxchat_input_copy_callback() {
6006 // Load the entire 'mxchat_options' array
6007 $all_options = get_option('mxchat_options', []);
6008
6009 // Retrieve the saved input copy or use the default value
6010 $default_copy = __('How can I assist?', 'mxchat');
6011 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6012
6013 // Output the input field with the saved value
6014 printf(
6015 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6016 esc_attr($input_copy),
6017 esc_attr__('How can I assist?', 'mxchat')
6018 );
6019
6020 // Output the description
6021 echo '<p class="description">' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '</p>';
6022 }
6023
6024
6025
6026 public function mxchat_user_message_font_color_callback() {
6027 $disabled = $this->is_activated ? '' : 'disabled';
6028 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6029
6030 echo '<div class="' . esc_attr($class) . '">';
6031 echo sprintf(
6032 '<input type="text"
6033 id="user_message_font_color"
6034 name="user_message_font_color"
6035 value="%s"
6036 class="my-color-field"
6037 data-default-color="#ffffff"
6038 %s />',
6039 isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff',
6040 esc_attr($disabled)
6041 );
6042
6043 if (!$this->is_activated) {
6044 echo '<div class="pro-feature-overlay">';
6045 echo '<a href="https://mxchat.ai/" target="_blank">';
6046 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6047 echo '</a>';
6048 echo '</div>';
6049 }
6050 echo '</div>';
6051 }
6052
6053 public function mxchat_bot_message_bg_color_callback() {
6054 $disabled = $this->is_activated ? '' : 'disabled';
6055 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6056
6057 echo '<div class="' . esc_attr($class) . '">';
6058 echo sprintf(
6059 '<input type="text"
6060 id="bot_message_bg_color"
6061 name="bot_message_bg_color"
6062 value="%s"
6063 class="my-color-field"
6064 data-default-color="#e1e1e1"
6065 %s />',
6066 isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1',
6067 esc_attr($disabled)
6068 );
6069
6070 if (!$this->is_activated) {
6071 echo '<div class="pro-feature-overlay">';
6072 echo '<a href="https://mxchat.ai/" target="_blank">';
6073 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6074 echo '</a>';
6075 echo '</div>';
6076 }
6077 echo '</div>';
6078 }
6079
6080 public function mxchat_bot_message_font_color_callback() {
6081 $disabled = $this->is_activated ? '' : 'disabled';
6082 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6083
6084 echo '<div class="' . esc_attr($class) . '">';
6085 echo sprintf(
6086 '<input type="text"
6087 id="bot_message_font_color"
6088 name="bot_message_font_color"
6089 value="%s"
6090 class="my-color-field"
6091 data-default-color="#333333"
6092 %s />',
6093 isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333',
6094 esc_attr($disabled)
6095 );
6096
6097 if (!$this->is_activated) {
6098 echo '<div class="pro-feature-overlay">';
6099 echo '<a href="https://mxchat.ai/" target="_blank">';
6100 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6101 echo '</a>';
6102 echo '</div>';
6103 }
6104 echo '</div>';
6105 }
6106
6107 public function mxchat_live_agent_message_bg_color_callback() {
6108 $disabled = $this->is_activated ? '' : 'disabled';
6109 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6110
6111 echo '<div class="' . esc_attr($class) . '">';
6112 echo sprintf(
6113 '<input type="text"
6114 id="live_agent_message_bg_color"
6115 name="live_agent_message_bg_color"
6116 value="%s"
6117 class="my-color-field"
6118 data-default-color="#ffffff"
6119 %s />',
6120 isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff',
6121 esc_attr($disabled)
6122 );
6123
6124 if (!$this->is_activated) {
6125 echo '<div class="pro-feature-overlay">';
6126 echo '<a href="https://mxchat.ai/" target="_blank">';
6127 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6128 echo '</a>';
6129 echo '</div>';
6130 }
6131 echo '</div>';
6132 }
6133
6134 public function mxchat_live_agent_message_font_color_callback() {
6135 $disabled = $this->is_activated ? '' : 'disabled';
6136 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6137
6138 echo '<div class="' . esc_attr($class) . '">';
6139 echo sprintf(
6140 '<input type="text"
6141 id="live_agent_message_font_color"
6142 name="live_agent_message_font_color"
6143 value="%s"
6144 class="my-color-field"
6145 data-default-color="#333333"
6146 %s />',
6147 isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333',
6148 esc_attr($disabled)
6149 );
6150
6151 if (!$this->is_activated) {
6152 echo '<div class="pro-feature-overlay">';
6153 echo '<a href="https://mxchat.ai/" target="_blank">';
6154 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6155 echo '</a>';
6156 echo '</div>';
6157 }
6158 echo '</div>';
6159 }
6160
6161 public function mxchat_mode_indicator_bg_color_callback() {
6162 $disabled = $this->is_activated ? '' : 'disabled';
6163 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6164
6165 echo '<div class="' . esc_attr($class) . '">';
6166 echo sprintf(
6167 '<input type="text"
6168 id="mode_indicator_bg_color"
6169 name="mode_indicator_bg_color"
6170 value="%s"
6171 class="my-color-field"
6172 data-default-color="#767676"
6173 %s />',
6174 isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676',
6175 esc_attr($disabled)
6176 );
6177
6178 if (!$this->is_activated) {
6179 echo '<div class="pro-feature-overlay">';
6180 echo '<a href="https://mxchat.ai/" target="_blank">';
6181 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6182 echo '</a>';
6183 echo '</div>';
6184 }
6185 echo '</div>';
6186 }
6187
6188 public function mxchat_mode_indicator_font_color_callback() {
6189 $disabled = $this->is_activated ? '' : 'disabled';
6190 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6191
6192 echo '<div class="' . esc_attr($class) . '">';
6193 echo sprintf(
6194 '<input type="text"
6195 id="mode_indicator_font_color"
6196 name="mode_indicator_font_color"
6197 value="%s"
6198 class="my-color-field"
6199 data-default-color="#ffffff"
6200 %s />',
6201 isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff',
6202 esc_attr($disabled)
6203 );
6204
6205 if (!$this->is_activated) {
6206 echo '<div class="pro-feature-overlay">';
6207 echo '<a href="https://mxchat.ai/" target="_blank">';
6208 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6209 echo '</a>';
6210 echo '</div>';
6211 }
6212 echo '</div>';
6213 }
6214
6215 public function mxchat_toolbar_icon_color_callback() {
6216 $disabled = $this->is_activated ? '' : 'disabled';
6217 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6218
6219 echo '<div class="' . esc_attr($class) . '">';
6220 echo sprintf(
6221 '<input type="text"
6222 id="toolbar_icon_color"
6223 name="toolbar_icon_color"
6224 value="%s"
6225 class="my-color-field"
6226 data-default-color="#212121"
6227 %s />',
6228 isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121',
6229 esc_attr($disabled)
6230 );
6231
6232 if (!$this->is_activated) {
6233 echo '<div class="pro-feature-overlay">';
6234 echo '<a href="https://mxchat.ai/" target="_blank">';
6235 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6236 echo '</a>';
6237 echo '</div>';
6238 }
6239 echo '</div>';
6240 }
6241
6242 public function mxchat_top_bar_bg_color_callback() {
6243 $disabled = $this->is_activated ? '' : 'disabled';
6244 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6245
6246 echo '<div class="' . esc_attr($class) . '">';
6247 echo sprintf(
6248 '<input type="text"
6249 id="top_bar_bg_color"
6250 name="top_bar_bg_color"
6251 value="%s"
6252 class="my-color-field"
6253 data-default-color="#00b294"
6254 %s />',
6255 isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294',
6256 esc_attr($disabled)
6257 );
6258
6259 if (!$this->is_activated) {
6260 echo '<div class="pro-feature-overlay">';
6261 echo '<a href="https://mxchat.ai/" target="_blank">';
6262 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6263 echo '</a>';
6264 echo '</div>';
6265 }
6266 echo '</div>';
6267 }
6268
6269 public function mxchat_send_button_font_color_callback() {
6270 $disabled = $this->is_activated ? '' : 'disabled';
6271 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6272
6273 echo '<div class="' . esc_attr($class) . '">';
6274 echo sprintf(
6275 '<input type="text"
6276 id="send_button_font_color"
6277 name="send_button_font_color"
6278 value="%s"
6279 class="my-color-field"
6280 data-default-color="#ffffff"
6281 %s />',
6282 isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff',
6283 esc_attr($disabled)
6284 );
6285
6286 if (!$this->is_activated) {
6287 echo '<div class="pro-feature-overlay">';
6288 echo '<a href="https://mxchat.ai/" target="_blank">';
6289 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6290 echo '</a>';
6291 echo '</div>';
6292 }
6293 echo '</div>';
6294 }
6295
6296 public function mxchat_chatbot_background_color_callback() {
6297 $disabled = $this->is_activated ? '' : 'disabled';
6298 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6299
6300 echo '<div class="' . esc_attr($class) . '">';
6301 echo sprintf(
6302 '<input type="text"
6303 id="chatbot_background_color"
6304 name="chatbot_background_color"
6305 value="%s"
6306 class="my-color-field"
6307 data-default-color="#000000"
6308 %s />',
6309 isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000',
6310 esc_attr($disabled)
6311 );
6312
6313 if (!$this->is_activated) {
6314 echo '<div class="pro-feature-overlay">';
6315 echo '<a href="https://mxchat.ai/" target="_blank">';
6316 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6317 echo '</a>';
6318 echo '</div>';
6319 }
6320 echo '</div>';
6321 }
6322
6323 public function mxchat_icon_color_callback() {
6324 $disabled = $this->is_activated ? '' : 'disabled';
6325 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6326
6327 echo '<div class="' . esc_attr($class) . '">';
6328 echo sprintf(
6329 '<input type="text"
6330 id="icon_color"
6331 name="icon_color"
6332 value="%s"
6333 class="my-color-field"
6334 data-default-color="#ffffff"
6335 %s />',
6336 isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff',
6337 esc_attr($disabled)
6338 );
6339
6340 if (!$this->is_activated) {
6341 echo '<div class="pro-feature-overlay">';
6342 echo '<a href="https://mxchat.ai/" target="_blank">';
6343 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6344 echo '</a>';
6345 echo '</div>';
6346 }
6347 echo '</div>';
6348 }
6349
6350 public function mxchat_custom_icon_callback() {
6351 $disabled = $this->is_activated ? '' : 'disabled';
6352 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6353 $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : '';
6354
6355 echo '<div class="' . esc_attr($class) . '">';
6356 echo sprintf(
6357 '<input type="url"
6358 id="custom_icon"
6359 name="custom_icon"
6360 value="%s"
6361 placeholder="%s"
6362 class="regular-text"
6363 %s />',
6364 $custom_icon_url,
6365 esc_attr__('Enter PNG URL', 'mxchat'),
6366 esc_attr($disabled)
6367 );
6368
6369 // Preview container for the icon
6370 if (!empty($custom_icon_url)) {
6371 echo '<div class="icon-preview" style="margin-top: 10px;">';
6372 echo '<img src="' . esc_url($custom_icon_url) . '" alt="' . esc_attr__('Custom Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6373 echo '</div>';
6374 }
6375
6376 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6377
6378 if (!$this->is_activated) {
6379 echo '<div class="pro-feature-overlay">';
6380 echo '<a href="https://mxchat.ai/" target="_blank">';
6381 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6382 echo '</a>';
6383 echo '</div>';
6384 }
6385 echo '</div>';
6386 }
6387
6388 public function mxchat_title_icon_callback() {
6389 $disabled = $this->is_activated ? '' : 'disabled';
6390 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6391 // Fixed the variable reference - it was using custom_icon instead of title_icon
6392 $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : '';
6393
6394 echo '<div class="' . esc_attr($class) . '">';
6395 echo sprintf(
6396 '<input type="url"
6397 id="title_icon"
6398 name="title_icon"
6399 value="%s"
6400 placeholder="%s"
6401 class="regular-text"
6402 %s />',
6403 $title_icon_url,
6404 esc_attr__('Enter PNG URL', 'mxchat'),
6405 esc_attr($disabled)
6406 );
6407
6408 // Preview container for the icon
6409 if (!empty($title_icon_url)) {
6410 echo '<div class="icon-preview" style="margin-top: 10px;">';
6411 echo '<img src="' . esc_url($title_icon_url) . '" alt="' . esc_attr__('Title Icon Preview', 'mxchat') . '" style="max-width: 48px; height: auto;" />';
6412 echo '</div>';
6413 }
6414
6415 echo '<p class="description">' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '</p>';
6416
6417 if (!$this->is_activated) {
6418 echo '<div class="pro-feature-overlay">';
6419 echo '<a href="https://mxchat.ai/" target="_blank">';
6420 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6421 echo '</a>';
6422 echo '</div>';
6423 }
6424 echo '</div>';
6425 }
6426
6427 public function mxchat_chat_input_font_color_callback() {
6428 $disabled = $this->is_activated ? '' : 'disabled';
6429 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6430
6431 echo '<div class="' . esc_attr($class) . '">';
6432 echo sprintf(
6433 '<input type="text"
6434 id="chat_input_font_color"
6435 name="chat_input_font_color"
6436 value="%s"
6437 class="my-color-field"
6438 data-default-color="#555555"
6439 %s />',
6440 isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555',
6441 esc_attr($disabled)
6442 );
6443
6444 if (!$this->is_activated) {
6445 echo '<div class="pro-feature-overlay">';
6446 echo '<a href="https://mxchat.ai/" target="_blank">';
6447 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6448 echo '</a>';
6449 echo '</div>';
6450 }
6451 echo '</div>';
6452 }
6453
6454 public function mxchat_append_to_body_callback() {
6455 // Get value from options array, default to 'off'
6456 $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off';
6457 $checked = ($append_to_body === 'on') ? 'checked' : '';
6458
6459 echo '<label class="toggle-switch">';
6460 echo sprintf(
6461 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
6462 esc_attr($checked)
6463 );
6464 echo '<span class="slider"></span>';
6465 echo '</label>';
6466 echo '<p class="description">' .
6467 esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') .
6468 '</p>';
6469
6470 }
6471
6472
6473
6474 public function mxchat_privacy_toggle_callback() {
6475 // Load from mxchat_options array
6476 $options = get_option('mxchat_options', []);
6477
6478 // Get privacy toggle value with fallback
6479 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
6480 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
6481
6482 // Get privacy text with fallback
6483 $privacy_text = isset($options['privacy_text'])
6484 ? $options['privacy_text']
6485 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
6486
6487 // Output the toggle switch
6488 echo '<label class="toggle-switch">';
6489 echo sprintf(
6490 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
6491 esc_attr($checked)
6492 );
6493 echo '<span class="slider"></span>';
6494 echo '</label>';
6495 echo '<p class="description">' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '</p>';
6496
6497 // Output the custom text input field
6498 echo sprintf(
6499 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
6500 esc_textarea($privacy_text)
6501 );
6502 echo '<p class="description">' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '</p>';
6503 }
6504
6505
6506 public function mxchat_complianz_toggle_callback() {
6507 // Load from mxchat_options array
6508 $options = get_option('mxchat_options', []);
6509
6510 // Get complianz toggle value with fallback
6511 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
6512 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
6513
6514 // Output the toggle switch
6515 echo '<label class="toggle-switch">';
6516 echo sprintf(
6517 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
6518 esc_attr($checked)
6519 );
6520 echo '<span class="slider"></span>';
6521 echo '</label>';
6522
6523 echo '<p class="description">' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '</p>';
6524 }
6525
6526 public function mxchat_link_target_toggle_callback() {
6527 // Load from mxchat_options array
6528 $options = get_option('mxchat_options', []);
6529
6530 // Get link target toggle value with fallback
6531 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
6532 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
6533
6534 // Output the toggle switch
6535 echo '<label class="toggle-switch">';
6536 echo sprintf(
6537 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
6538 esc_attr($checked)
6539 );
6540 echo '<span class="slider"></span>';
6541 echo '</label>';
6542 echo '<p class="description">' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '</p>';
6543 }
6544
6545 public function mxchat_chat_persistence_toggle_callback() {
6546 // Load from mxchat_options array
6547 $options = get_option('mxchat_options', []);
6548
6549 // Get chat persistence toggle value with fallback
6550 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
6551 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
6552
6553 // Output the toggle switch
6554 echo '<label class="toggle-switch">';
6555 echo sprintf(
6556 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
6557 esc_attr($checked)
6558 );
6559 echo '<span class="slider"></span>';
6560 echo '</label>';
6561
6562 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>';
6563 }
6564
6565 public function mxchat_popular_question_1_callback() {
6566 // Load the full plugin options array
6567 $all_options = get_option('mxchat_options', []);
6568
6569 // Retrieve the specific option for popular_question_1
6570 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
6571
6572 // Render the input field
6573 printf(
6574 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
6575 esc_attr($popular_question_1),
6576 esc_attr__('Enter Quick Question 1', 'mxchat')
6577 );
6578
6579 // Add a description for the field
6580 echo '<p class="description">' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '</p>';
6581 }
6582
6583
6584 public function mxchat_popular_question_2_callback() {
6585 // Load the full plugin options array
6586 $all_options = get_option('mxchat_options', []);
6587
6588 // Retrieve the specific option for popular_question_2
6589 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
6590
6591 // Render the input field
6592 printf(
6593 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
6594 esc_attr($popular_question_2),
6595 esc_attr__('Enter Quick Question 2', 'mxchat')
6596 );
6597
6598 // Add a description for the field
6599 echo '<p class="description">' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '</p>';
6600 }
6601
6602
6603 public function mxchat_popular_question_3_callback() {
6604 // Load the full plugin options array
6605 $all_options = get_option('mxchat_options', []);
6606
6607 // Retrieve the specific option for popular_question_3
6608 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
6609
6610 // Render the input field
6611 printf(
6612 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
6613 esc_attr($popular_question_3),
6614 esc_attr(__('Enter Quick Question 3', 'mxchat'))
6615 );
6616
6617 // Add a description for the field
6618 echo '<p class="description">' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '</p>';
6619 }
6620
6621 public function mxchat_additional_popular_questions_callback() {
6622 $options = get_option('mxchat_options', []);
6623 $additional_questions = isset($options['additional_popular_questions'])
6624 ? $options['additional_popular_questions']
6625 : get_option('additional_popular_questions', array());
6626
6627 echo '<div id="mxchat-additional-questions-container">';
6628 if (!empty($additional_questions)) {
6629 foreach ($additional_questions as $index => $question) {
6630 printf(
6631 '<div class="mxchat-question-row">
6632 <input type="text" name="additional_popular_questions[]"
6633 value="%s"
6634 placeholder="%s"
6635 class="regular-text mxchat-question-input"
6636 data-question-index="%d" />
6637 <button type="button" class="button mxchat-remove-question"
6638 aria-label="%s">%s</button>
6639 </div>',
6640 esc_attr($question),
6641 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
6642 $index,
6643 esc_attr(__('Remove question', 'mxchat')),
6644 esc_html__('Remove', 'mxchat')
6645 );
6646 }
6647 } else {
6648 printf(
6649 '<div class="mxchat-question-row">
6650 <input type="text" name="additional_popular_questions[]"
6651 value=""
6652 placeholder="%s"
6653 class="regular-text mxchat-question-input"
6654 data-question-index="0" />
6655 <button type="button" class="button mxchat-remove-question"
6656 aria-label="%s">%s</button>
6657 </div>',
6658 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
6659 esc_attr(__('Remove question', 'mxchat')),
6660 esc_html__('Remove', 'mxchat')
6661 );
6662 }
6663 echo '</div>';
6664 printf(
6665 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
6666 esc_attr(__('Add question', 'mxchat')),
6667 esc_html__('Add Question', 'mxchat')
6668 );
6669 echo '<p class="description">' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '</p>';
6670 echo '</div>';
6671 }
6672
6673 public function mxchat_brave_api_key_callback() {
6674 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
6675
6676 echo '<div class="api-key-wrapper">';
6677 echo sprintf(
6678 '<input type="password" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text" />',
6679 $brave_api_key
6680 );
6681 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6682 echo '</div>';
6683 echo '<p class="description">' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '</p>';
6684 }
6685
6686 public function mxchat_brave_image_count_callback() {
6687 $brave_image_count = isset($this->options['brave_image_count'])
6688 ? intval($this->options['brave_image_count'])
6689 : 4;
6690
6691 echo sprintf(
6692 '<input type="number" id="brave_image_count" name="brave_image_count"
6693 value="%d" min="1" max="6" class="small-text" />',
6694 $brave_image_count
6695 );
6696 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
6697 }
6698
6699 public function mxchat_brave_safe_search_callback() {
6700 $brave_safe_search = isset($this->options['brave_safe_search'])
6701 ? esc_attr($this->options['brave_safe_search'])
6702 : 'strict';
6703
6704 echo '<select id="brave_safe_search" name="brave_safe_search">';
6705 echo sprintf(
6706 '<option value="strict" %s>%s</option>',
6707 selected($brave_safe_search, 'strict', false),
6708 __('Strict', 'mxchat')
6709 );
6710 echo sprintf(
6711 '<option value="off" %s>%s</option>',
6712 selected($brave_safe_search, 'off', false),
6713 __('Off', 'mxchat')
6714 );
6715 echo '</select>';
6716 echo '<p class="description">' .
6717 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
6718 '</p>';
6719 }
6720
6721 public function mxchat_brave_news_count_callback() {
6722 $brave_news_count = isset($this->options['brave_news_count'])
6723 ? intval($this->options['brave_news_count'])
6724 : 3;
6725
6726 echo sprintf(
6727 '<input type="number" id="brave_news_count" name="brave_news_count"
6728 value="%d" min="1" max="10" class="small-text" />',
6729 $brave_news_count
6730 );
6731 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
6732 }
6733
6734 public function mxchat_brave_country_callback() {
6735 $brave_country = isset($this->options['brave_country'])
6736 ? esc_attr($this->options['brave_country'])
6737 : 'us';
6738
6739 echo sprintf(
6740 '<input type="text" id="brave_country" name="brave_country"
6741 value="%s" maxlength="2" class="small-text" />',
6742 $brave_country
6743 );
6744 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
6745 }
6746
6747 public function mxchat_brave_language_callback() {
6748 $brave_language = isset($this->options['brave_language'])
6749 ? esc_attr($this->options['brave_language'])
6750 : 'en';
6751
6752 echo sprintf(
6753 '<input type="text" id="brave_language" name="brave_language"
6754 value="%s" maxlength="2" class="small-text" />',
6755 $brave_language
6756 );
6757 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
6758 }
6759
6760
6761
6762
6763
6764 // Section Callback
6765 public function mxchat_pdf_intent_section_callback() {
6766 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
6767 }
6768
6769 public function mxchat_chat_toolbar_toggle_callback() {
6770 // Get chat toolbar toggle value with fallback
6771 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6772 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6773
6774 // Check if the plugin is activated (paid feature)
6775 $disabled = $this->is_activated ? '' : 'disabled';
6776 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6777
6778 echo '<div class="' . esc_attr($class) . '">';
6779
6780 // Output the toggle switch
6781 echo '<label class="toggle-switch">';
6782 echo sprintf(
6783 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s %s />',
6784 esc_attr($checked),
6785 esc_attr($disabled)
6786 );
6787 echo '<span class="slider"></span>';
6788 echo '</label>';
6789
6790 // Pro feature overlay
6791 if (!$this->is_activated) {
6792 echo '<div class="pro-feature-overlay">';
6793 echo '<a href="https://mxchat.ai/" target="_blank">';
6794 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6795 echo '</a>';
6796 echo '</div>';
6797 }
6798
6799 echo '</div>';
6800 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>';
6801 }
6802
6803 /**
6804 * Callback for PDF upload button toggle setting
6805 */
6806 public function mxchat_show_pdf_upload_button_callback() {
6807 // Get toggle value with fallback
6808 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6809 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
6810 // Check if the plugin is activated (paid feature)
6811 $disabled = $this->is_activated ? '' : 'disabled';
6812 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6813
6814 echo '<div class="' . esc_attr($class) . '">';
6815 // Output the toggle switch
6816 echo '<label class="toggle-switch">';
6817 echo sprintf(
6818 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s %s />',
6819 esc_attr($checked),
6820 esc_attr($disabled)
6821 );
6822 echo '<span class="slider"></span>';
6823 echo '</label>';
6824
6825 // Pro feature overlay if needed
6826 if (!$this->is_activated) {
6827 echo '<div class="pro-feature-overlay">';
6828 echo '<a href="https://mxchat.ai/" target="_blank">';
6829 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6830 echo '</a>';
6831 echo '</div>';
6832 }
6833
6834 echo '</div>';
6835 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6836 }
6837
6838 /**
6839 * Callback for Word upload button toggle setting
6840 */
6841 public function mxchat_show_word_upload_button_callback() {
6842 // Get toggle value with fallback
6843 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6844 $checked = ($show_word_button === 'on') ? 'checked' : '';
6845 // Check if the plugin is activated (paid feature)
6846 $disabled = $this->is_activated ? '' : 'disabled';
6847 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6848
6849 echo '<div class="' . esc_attr($class) . '">';
6850 // Output the toggle switch
6851 echo '<label class="toggle-switch">';
6852 echo sprintf(
6853 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s %s />',
6854 esc_attr($checked),
6855 esc_attr($disabled)
6856 );
6857 echo '<span class="slider"></span>';
6858 echo '</label>';
6859
6860 // Pro feature overlay if needed
6861 if (!$this->is_activated) {
6862 echo '<div class="pro-feature-overlay">';
6863 echo '<a href="https://mxchat.ai/" target="_blank">';
6864 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6865 echo '</a>';
6866 echo '</div>';
6867 }
6868
6869 echo '</div>';
6870 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6871 }
6872
6873 public function mxchat_pdf_intent_trigger_text_callback() {
6874 $disabled = $this->is_activated ? '' : 'disabled';
6875 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6876 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
6877
6878 echo '<div class="' . esc_attr($class) . '">';
6879 echo sprintf(
6880 '<textarea id="pdf_intent_trigger_text"
6881 name="pdf_intent_trigger_text"
6882 rows="3"
6883 cols="50"
6884 placeholder="%s"
6885 %s>%s</textarea>',
6886 esc_attr__('Enter trigger text', 'mxchat'),
6887 esc_attr($disabled),
6888 isset($this->options['pdf_intent_trigger_text'])
6889 ? esc_textarea($this->options['pdf_intent_trigger_text'])
6890 : esc_textarea($default_text)
6891 );
6892 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
6893
6894 if (!$this->is_activated) {
6895 echo '<div class="pro-feature-overlay">';
6896 echo '<a href="https://mxchat.ai/" target="_blank">';
6897 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6898 echo '</a>';
6899 echo '</div>';
6900 }
6901 echo '</div>';
6902 }
6903
6904 public function mxchat_pdf_intent_success_text_callback() {
6905 $disabled = $this->is_activated ? '' : 'disabled';
6906 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6907 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
6908
6909 echo '<div class="' . esc_attr($class) . '">';
6910 echo sprintf(
6911 '<textarea id="pdf_intent_success_text"
6912 name="pdf_intent_success_text"
6913 rows="3"
6914 cols="50"
6915 placeholder="%s"
6916 %s>%s</textarea>',
6917 esc_attr__('Enter success text', 'mxchat'),
6918 esc_attr($disabled),
6919 isset($this->options['pdf_intent_success_text'])
6920 ? esc_textarea($this->options['pdf_intent_success_text'])
6921 : esc_textarea($default_text)
6922 );
6923 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
6924
6925 if (!$this->is_activated) {
6926 echo '<div class="pro-feature-overlay">';
6927 echo '<a href="https://mxchat.ai/" target="_blank">';
6928 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6929 echo '</a>';
6930 echo '</div>';
6931 }
6932 echo '</div>';
6933 }
6934
6935 public function mxchat_pdf_intent_error_text_callback() {
6936 $disabled = $this->is_activated ? '' : 'disabled';
6937 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6938 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
6939
6940 echo '<div class="' . esc_attr($class) . '">';
6941 echo sprintf(
6942 '<textarea id="pdf_intent_error_text"
6943 name="pdf_intent_error_text"
6944 rows="3"
6945 cols="50"
6946 placeholder="%s"
6947 %s>%s</textarea>',
6948 esc_attr__('Enter error text', 'mxchat'),
6949 esc_attr($disabled),
6950 isset($this->options['pdf_intent_error_text'])
6951 ? esc_textarea($this->options['pdf_intent_error_text'])
6952 : esc_textarea($default_text)
6953 );
6954 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
6955
6956 if (!$this->is_activated) {
6957 echo '<div class="pro-feature-overlay">';
6958 echo '<a href="https://mxchat.ai/" target="_blank">';
6959 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6960 echo '</a>';
6961 echo '</div>';
6962 }
6963 echo '</div>';
6964 }
6965
6966 public function mxchat_pdf_max_pages_callback() {
6967 $disabled = $this->is_activated ? '' : 'disabled';
6968 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
6969 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
6970
6971 echo '<div class="' . esc_attr($class) . '">';
6972 echo sprintf(
6973 '<input type="range"
6974 id="pdf_max_pages"
6975 name="pdf_max_pages"
6976 min="1"
6977 max="69"
6978 value="%d"
6979 %s
6980 class="range-slider" />',
6981 esc_attr($max_pages),
6982 esc_attr($disabled)
6983 );
6984 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
6985 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
6986
6987 if (!$this->is_activated) {
6988 echo '<div class="pro-feature-overlay">';
6989 echo '<a href="https://mxchat.ai/" target="_blank">';
6990 echo '<img src="' . esc_url(plugin_dir_url(__FILE__) . '../images/pro-only-dark.png') . '" alt="' . esc_attr__('Pro Only', 'mxchat') . '" />';
6991 echo '</a>';
6992 echo '</div>';
6993 }
6994 echo '</div>';
6995 }
6996
6997 public function mxchat_live_agent_status_callback() {
6998 $disabled = $this->is_activated ? '' : 'disabled';
6999 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7000 $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off';
7001
7002 echo '<div class="' . esc_attr($class) . '">';
7003 echo '<label class="toggle-switch">';
7004 echo sprintf(
7005 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s %s />',
7006 checked($status, 'on', false),
7007 esc_attr($disabled)
7008 );
7009 echo '<span class="slider"></span>';
7010 echo '</label>';
7011 echo '<label for="live_agent_status" class="mxchat-status-label">';
7012 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
7013 echo '</label>';
7014
7015 if (!$this->is_activated) {
7016 echo '<div class="pro-feature-overlay">';
7017 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>';
7018 echo '</div>';
7019 }
7020 echo '</div>';
7021 }
7022
7023
7024 // Away Message Callback
7025 public function mxchat_live_agent_away_message_callback() {
7026 $disabled = $this->is_activated ? '' : 'disabled';
7027 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7028 $message = isset($this->options['live_agent_away_message'])
7029 ? $this->options['live_agent_away_message']
7030 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
7031
7032 echo '<div class="' . esc_attr($class) . '">';
7033 printf(
7034 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50" %s>%s</textarea>',
7035 esc_attr($disabled),
7036 esc_textarea($message)
7037 );
7038 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
7039
7040 if (!$this->is_activated) {
7041 echo '<div class="pro-feature-overlay">';
7042 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>';
7043 echo '</div>';
7044 }
7045 echo '</div>';
7046 }
7047
7048 public function mxchat_live_agent_notification_message_callback() {
7049 $disabled = $this->is_activated ? '' : 'disabled';
7050 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7051 $message = isset($this->options['live_agent_notification_message'])
7052 ? $this->options['live_agent_notification_message']
7053 : __('Live agent has been notified.', 'mxchat');
7054
7055 echo '<div class="' . esc_attr($class) . '">';
7056 printf(
7057 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50" %s>%s</textarea>',
7058 esc_attr($disabled),
7059 esc_textarea($message)
7060 );
7061 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
7062
7063 if (!$this->is_activated) {
7064 echo '<div class="pro-feature-overlay">';
7065 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>';
7066 echo '</div>';
7067 }
7068 echo '</div>';
7069 }
7070
7071 public function mxchat_live_agent_webhook_url_callback() {
7072 $disabled = $this->is_activated ? '' : 'disabled';
7073 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7074 $webhook_url = isset($this->options['live_agent_webhook_url'])
7075 ? esc_url($this->options['live_agent_webhook_url'])
7076 : esc_url(get_option('live_agent_webhook_url', ''));
7077
7078 echo '<div class="' . esc_attr($class) . '">';
7079 printf(
7080 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" %s />',
7081 $webhook_url,
7082 esc_attr($disabled)
7083 );
7084 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7085 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
7086
7087 if (!$this->is_activated) {
7088 echo '<div class="pro-feature-overlay">';
7089 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>';
7090 echo '</div>';
7091 }
7092 echo '</div>';
7093 }
7094
7095 public function mxchat_similarity_threshold_callback() {
7096 // Load from mxchat_options array
7097 $options = get_option('mxchat_options', []);
7098
7099 // Get value from options array with default of 80
7100 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
7101
7102 echo '<div class="slider-container">';
7103 echo sprintf(
7104 '<input type="range"
7105 id="similarity_threshold"
7106 name="similarity_threshold"
7107 min="20"
7108 max="85"
7109 step="1"
7110 value="%s"
7111 class="range-slider" />',
7112 esc_attr($threshold)
7113 );
7114 echo sprintf(
7115 '<span id="threshold_value" class="range-value">%s</span>',
7116 esc_html($threshold)
7117 );
7118 echo '</div>';
7119 echo '<p class="description">';
7120 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');
7121 echo '</p>';
7122 }
7123
7124
7125 public function mxchat_live_agent_secret_key_callback() {
7126 $disabled = $this->is_activated ? '' : 'disabled';
7127 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7128
7129 echo '<div class="' . esc_attr($class) . '">';
7130 printf(
7131 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" %s />',
7132 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '',
7133 esc_attr($disabled)
7134 );
7135 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7136 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
7137
7138 if (!$this->is_activated) {
7139 echo '<div class="pro-feature-overlay">';
7140 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>';
7141 echo '</div>';
7142 }
7143 echo '</div>';
7144 }
7145
7146 public function mxchat_live_agent_bot_token_callback() {
7147 $disabled = $this->is_activated ? '' : 'disabled';
7148 $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive';
7149
7150 echo '<div class="' . esc_attr($class) . '">';
7151 printf(
7152 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" %s />',
7153 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '',
7154 esc_attr($disabled)
7155 );
7156 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
7157 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
7158
7159 if (!$this->is_activated) {
7160 echo '<div class="pro-feature-overlay">';
7161 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>';
7162 echo '</div>';
7163 }
7164 echo '</div>';
7165 }
7166
7167 public function mxchat_enqueue_admin_assets() {
7168 wp_enqueue_style('wp-color-picker');
7169
7170 // Get the plugin version or file modification time for cache busting
7171 $plugin_version = '2.1.5'; // Replace this with your plugin's version
7172
7173 // File paths
7174 $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js';
7175 $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js';
7176 $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css';
7177 $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css';
7178 $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css';
7179 $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css';
7180 $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js';
7181
7182 // Check if files exist and get modification times
7183 $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version;
7184 $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version;
7185 $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version;
7186 $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version;
7187 $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version;
7188 $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version;
7189 $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version;
7190
7191 $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js';
7192 $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version;
7193
7194 // Check current admin page
7195 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
7196
7197 // Only enqueue on the prompts page
7198 if ($current_page === 'mxchat-prompts') {
7199 wp_enqueue_script(
7200 'mxchat-status-updater',
7201 plugin_dir_url(__FILE__) . '../js/admin-status.js',
7202 array('jquery'),
7203 $admin_status_js_version,
7204 true
7205 );
7206
7207 // Add the nonce for the status updater
7208 wp_localize_script(
7209 'mxchat-status-updater',
7210 'mxchat_status_data',
7211 array(
7212 'ajax_url' => admin_url('admin-ajax.php'),
7213 'nonce' => wp_create_nonce('mxchat_status_nonce')
7214 )
7215 );
7216 }
7217
7218 // Enqueue scripts and styles with corrected paths
7219 wp_enqueue_script(
7220 'mxchat-color-picker',
7221 plugin_dir_url(__FILE__) . '../js/my-color-picker.js',
7222 array('wp-color-picker'),
7223 $color_picker_version,
7224 true
7225 );
7226
7227 wp_enqueue_script(
7228 'mxchat-embedding-check',
7229 plugin_dir_url(__FILE__) . '../js/embedding-check.js',
7230 array(),
7231 $embedding_check_version,
7232 true
7233 );
7234
7235 wp_enqueue_script(
7236 'mxchat-admin-js',
7237 plugin_dir_url(__FILE__) . '../js/mxchat-admin.js',
7238 array('jquery'),
7239 $plugin_version,
7240 true
7241 );
7242
7243 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array(
7244 'ajax_url' => admin_url('admin-ajax.php'),
7245 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7246 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7247 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7248 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7249 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7250 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7251 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7252 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7253 'is_activated' => $this->is_activated ? '1' : '0',
7254 // Add these two new lines
7255 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7256 'status_refresh_interval' => 5000, // Update every 5 seconds
7257 ));
7258
7259 // Enqueue the admin CSS
7260 wp_enqueue_style(
7261 'mxchat-admin-css',
7262 plugin_dir_url(__FILE__) . '../css/admin-style.css',
7263 array(),
7264 $admin_css_version
7265 );
7266
7267 // Conditional enqueue for transcripts page
7268 if ($current_page === 'mxchat-transcripts') {
7269 wp_enqueue_style(
7270 'mxchat-chat-transcripts-css',
7271 plugin_dir_url(__FILE__) . '../css/chat-transcripts.css',
7272 array(),
7273 $transcripts_css_version
7274 );
7275
7276 wp_enqueue_script(
7277 'mxchat-transcripts-js',
7278 plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js',
7279 array('jquery'),
7280 $transcripts_js_version,
7281 true
7282 );
7283 }
7284
7285 // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages
7286 if (strpos($current_page, 'mxchat') !== false) {
7287 wp_enqueue_style(
7288 'mxchat-knowledge-css',
7289 plugin_dir_url(__FILE__) . '../css/knowledge-style.css',
7290 array(),
7291 $knowledge_css_version
7292 );
7293 }
7294
7295 // Only enqueue intent-style.css on the mxchat-actions page
7296 if ($current_page === 'mxchat-actions') {
7297 wp_enqueue_style(
7298 'mxchat-intent-css',
7299 plugin_dir_url(__FILE__) . '../css/intent-style.css',
7300 array(),
7301 $intent_css_version
7302 );
7303 }
7304
7305 // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin
7306 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7307 'ajax_url' => admin_url('admin-ajax.php'),
7308 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7309 ));
7310
7311 // Localize the script for color picker and settings
7312 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7313 'ajax_url' => admin_url('admin-ajax.php'),
7314 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
7315 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
7316 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
7317 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
7318 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
7319 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
7320 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
7321 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
7322 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
7323 'icon_color' => $this->options['icon_color'] ?? '#fff',
7324 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
7325 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
7326 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
7327 'loops_api_key' => $this->options['loops_api_key'] ?? '',
7328 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
7329 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7330 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7331 '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'),
7332 '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'),
7333 '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'),
7334 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7335 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7336 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7337 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7338 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7339 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7340 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7341 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7342 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7343 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7344 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7345 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7346 ));
7347 }
7348
7349
7350 public function mxchat_sanitize($input) {
7351 $new_input = array();
7352
7353 if (isset($input['api_key'])) {
7354 $new_input['api_key'] = sanitize_text_field($input['api_key']);
7355 }
7356
7357 if (isset($input['similarity_threshold'])) {
7358 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
7359 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
7360 }
7361
7362 if (isset($input['xai_api_key'])) {
7363 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
7364 }
7365
7366 if (isset($input['claude_api_key'])) {
7367 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
7368 }
7369
7370 if (isset($input['deepseek_api_key'])) {
7371 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
7372 }
7373
7374 if (isset($input['gemini_api_key'])) {
7375 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7376 }
7377
7378 if (isset($input['enable_woocommerce_integration'])) {
7379 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
7380 }
7381
7382 if (isset($input['privacy_toggle'])) {
7383 $new_input['privacy_toggle'] = $input['privacy_toggle'];
7384 }
7385
7386 if (isset($input['complianz_toggle'])) {
7387 $new_input['complianz_toggle'] = $input['complianz_toggle'];
7388 }
7389
7390 // Handle custom privacy text input
7391 if (isset($input['privacy_text'])) {
7392 // Allow basic HTML for links
7393 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
7394 }
7395
7396 if (isset($input['system_prompt_instructions'])) {
7397 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
7398 }
7399
7400 if (isset($input['mxchat_pro_email'])) {
7401 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
7402 }
7403
7404 if (isset($input['mxchat_activation_key'])) {
7405 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
7406 }
7407
7408 if (isset($input['append_to_body'])) {
7409 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
7410 }
7411
7412 if (isset($input['top_bar_title'])) {
7413 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
7414 }
7415
7416 if (isset($input['ai_agent_text'])) {
7417 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7418 }
7419
7420 if (isset($input['enable_email_block'])) {
7421 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
7422 }
7423
7424 if (isset($input['email_blocker_header_content'])) {
7425 // wp_kses_post() allows standard HTML tags permitted by WordPress
7426 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
7427 }
7428 if (isset($input['email_blocker_button_text'])) {
7429 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
7430 }
7431
7432 if (isset($input['intro_message'])) {
7433 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
7434 }
7435
7436 if (isset($input['input_copy'])) {
7437 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
7438 }
7439
7440 if (isset($input['rate_limit_message'])) {
7441 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
7442 }
7443
7444 if (isset($input['rate_limit_logged_out'])) {
7445 $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values
7446 $rate_limit = sanitize_text_field($input['rate_limit_logged_out']);
7447
7448 if (in_array($rate_limit, $allowed_limits, true)) {
7449 $new_input['rate_limit_logged_out'] = $rate_limit;
7450 } else {
7451 $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users
7452 }
7453 }
7454
7455 // Handle role rate limits
7456 if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) {
7457 $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited');
7458 $new_input['role_rate_limits'] = array();
7459
7460 foreach ($input['role_rate_limits'] as $role_id => $rate_limit) {
7461 $rate_limit = sanitize_text_field($rate_limit);
7462 if (in_array($rate_limit, $allowed_limits, true)) {
7463 $new_input['role_rate_limits'][$role_id] = $rate_limit;
7464 } else {
7465 $new_input['role_rate_limits'][$role_id] = '100'; // Default
7466 }
7467 }
7468 }
7469
7470 if (isset($input['pre_chat_message'])) {
7471 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
7472 }
7473
7474 if (isset($input['voyage_api_key'])) {
7475 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7476 }
7477
7478 // Add to your sanitize function
7479 if (isset($input['embedding_model'])) {
7480 $allowed_models = array(
7481 'text-embedding-ada-002',
7482 'text-embedding-3-small',
7483 'text-embedding-3-large',
7484 'voyage-3-large'
7485 );
7486 if (in_array($input['embedding_model'], $allowed_models)) {
7487 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7488 }
7489 }
7490
7491 if (isset($input['model'])) {
7492 $allowed_models = array(
7493 'gemini-2.0-flash',
7494 'gemini-2.0-flash-lite',
7495 'gemini-1.5-pro',
7496 'gemini-1.5-flash',
7497 'grok-beta',
7498 'grok-2',
7499 'deepseek-chat',
7500 'claude-3-7-sonnet-20250219',
7501 'claude-3-5-sonnet-20241022',
7502 'claude-3-opus-20240229',
7503 'claude-3-sonnet-20240229',
7504 'claude-3-haiku-20240307',
7505 'gpt-4o',
7506 'gpt-4o-mini',
7507 'gpt-4-turbo',
7508 'gpt-4',
7509 'gpt-3.5-turbo',
7510 );
7511 if (in_array($input['model'], $allowed_models)) {
7512 $new_input['model'] = sanitize_text_field($input['model']);
7513 }
7514 }
7515
7516 if (isset($input['close_button_color'])) {
7517 $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']);
7518 }
7519
7520 if (isset($input['chatbot_bg_color'])) {
7521 $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']);
7522 }
7523
7524 if (isset($input['woocommerce_consumer_key'])) {
7525 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
7526 }
7527
7528 if (isset($input['woocommerce_consumer_secret'])) {
7529 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
7530 }
7531
7532 if (isset($input['user_message_bg_color'])) {
7533 $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']);
7534 }
7535
7536 if (isset($input['user_message_font_color'])) {
7537 $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']);
7538 }
7539
7540 if (isset($input['bot_message_bg_color'])) {
7541 $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']);
7542 }
7543
7544 if (isset($input['bot_message_font_color'])) {
7545 $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']);
7546 }
7547
7548 if (isset($input['live_agent_message_bg_color'])) {
7549 $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']);
7550 }
7551
7552 if (isset($input['live_agent_message_font_color'])) {
7553 $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']);
7554 }
7555
7556 if (isset($input['mode_indicator_bg_color'])) {
7557 $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']);
7558 }
7559
7560 if (isset($input['mode_indicator_font_color'])) {
7561 $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']);
7562 }
7563
7564 if (isset($input['toolbar_icon_color'])) {
7565 $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']);
7566 }
7567
7568 if (isset($input['top_bar_bg_color'])) {
7569 $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']);
7570 }
7571
7572 if (isset($input['send_button_font_color'])) {
7573 $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']);
7574 }
7575
7576 if (isset($input['chatbot_background_color'])) {
7577 $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']);
7578 }
7579
7580 if (isset($input['icon_color'])) {
7581 $new_input['icon_color'] = sanitize_hex_color($input['icon_color']);
7582 }
7583
7584 if (isset($input['custom_icon'])) {
7585 $new_input['custom_icon'] = esc_url_raw($input['custom_icon']);
7586 }
7587
7588 if (isset($input['title_icon'])) {
7589 $new_input['title_icon'] = esc_url_raw($input['title_icon']);
7590 }
7591
7592 if (isset($input['chat_input_font_color'])) {
7593 $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']);
7594 }
7595
7596 // Sanitize link_target_toggle
7597 if (isset($input['link_target_toggle'])) {
7598 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
7599 }
7600
7601 // Sanitize Loops API Key
7602 if (isset($input['loops_api_key'])) {
7603 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
7604 }
7605
7606 if (isset($input['chat_persistence_toggle'])) {
7607 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
7608 }
7609
7610 if (isset($input['popular_question_1'])) {
7611 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
7612 }
7613
7614 if (isset($input['popular_question_2'])) {
7615 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
7616 }
7617
7618 if (isset($input['popular_question_3'])) {
7619 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
7620 }
7621
7622 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
7623 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
7624 }
7625
7626 // Sanitize Loops Mailing List
7627 if (isset($input['loops_mailing_list'])) {
7628 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
7629 }
7630
7631 // Sanitize Triggered Phrase Response
7632 if (isset($input['triggered_phrase_response'])) {
7633 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
7634 }
7635
7636 if (isset($input['email_capture_response'])) {
7637 $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']);
7638 }
7639
7640 // Sanitize Brave Search Settings
7641 if (isset($input['brave_api_key'])) {
7642 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
7643 }
7644
7645 if (isset($input['brave_image_count'])) {
7646 $image_count = intval($input['brave_image_count']);
7647 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
7648 }
7649
7650 if (isset($input['brave_safe_search'])) {
7651 $allowed = array('strict', 'off');
7652 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
7653 }
7654
7655 if (isset($input['brave_news_count'])) {
7656 $news_count = intval($input['brave_news_count']);
7657 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
7658 }
7659
7660 if (isset($input['brave_country'])) {
7661 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
7662 }
7663
7664 if (isset($input['brave_language'])) {
7665 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
7666 }
7667
7668 if (isset($input['chat_toolbar_toggle'])) {
7669 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
7670 }
7671
7672 // Sanitize PDF upload button toggle
7673 if (isset($input['show_pdf_upload_button'])) {
7674 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7675 } else {
7676 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7677 }
7678
7679 // Sanitize Word upload button toggle
7680 if (isset($input['show_word_upload_button'])) {
7681 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7682 } else {
7683 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7684 }
7685
7686 if (isset($input['pdf_intent_trigger_text'])) {
7687 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
7688 }
7689
7690 if (isset($input['pdf_intent_success_text'])) {
7691 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
7692 }
7693
7694 if (isset($input['pdf_intent_error_text'])) {
7695 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
7696 }
7697
7698 if (isset($input['pdf_max_pages'])) {
7699 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
7700 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
7701 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
7702 }
7703 }
7704
7705 if (isset($input['live_agent_webhook_url'])) {
7706 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
7707 }
7708 if (isset($input['live_agent_secret_key'])) {
7709 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
7710 }
7711
7712 // Live Agent Integration
7713 if (isset($input['live_agent_bot_token'])) {
7714 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
7715 }
7716
7717 if (isset($input['live_agent_status'])) {
7718 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
7719 }
7720 if (isset($input['live_agent_away_message'])) {
7721 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
7722 }
7723 if (isset($input['live_agent_notification_message'])) {
7724 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
7725 }
7726
7727 return $new_input;
7728 }
7729
7730
7731 // Method to append the chatbot to the body
7732 public function mxchat_append_chatbot_to_body() {
7733 $options = get_option('mxchat_options');
7734 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
7735 echo do_shortcode('[mxchat_chatbot floating="yes"]');
7736 }
7737 }
7738
7739
7740
7741
7742
7743 private function mxchat_fetch_loops_mailing_lists($api_key) {
7744 $url = 'https://app.loops.so/api/v1/lists';
7745 $response = wp_remote_get($url, array(
7746 'headers' => array(
7747 'Authorization' => 'Bearer ' . $api_key,
7748 'Content-Type' => 'application/json'
7749 )
7750 ));
7751
7752 if (is_wp_error($response)) {
7753 return array();
7754 }
7755
7756 $body = wp_remote_retrieve_body($response);
7757 $lists = json_decode($body, true);
7758
7759 return isset($lists) && is_array($lists) ? $lists : array();
7760 }
7761
7762
7763
7764
7765
7766 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
7767 if (empty($vec1) || empty($vec2)) {
7768 return 0.0;
7769 }
7770
7771 $dot_product = 0.0;
7772 $norm_a = 0.0;
7773 $norm_b = 0.0;
7774
7775 for ($i = 0; $i < count($vec1); $i++) {
7776 $dot_product += $vec1[$i] * $vec2[$i];
7777 $norm_a += pow($vec1[$i], 2);
7778 $norm_b += pow($vec2[$i], 2);
7779 }
7780
7781 if ($norm_a == 0.0 || $norm_b == 0.0) {
7782 return 0.0;
7783 } else {
7784 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
7785 }
7786 }
7787
7788
7789 /**
7790 * Update your existing display_admin_notices function to show notices on all MXChat pages
7791 */
7792 public function display_admin_notices() {
7793 // Check if we're on a MXChat admin page
7794 $screen = get_current_screen();
7795 if (!$screen || strpos($screen->base, 'mxchat') === false) {
7796 return;
7797 }
7798
7799 //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base);
7800
7801 // Check for error notices
7802 $error_notice = get_transient('mxchat_admin_notice_error');
7803 if ($error_notice) {
7804 //error_log('Found error transient: ' . $error_notice);
7805 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p></div>';
7806 delete_transient('mxchat_admin_notice_error');
7807 //error_log('Displayed and deleted error transient');
7808 } else {
7809 //error_log('No error transient found');
7810 }
7811
7812 // Check for success notices
7813 $success_notice = get_transient('mxchat_admin_notice_success');
7814 if ($success_notice) {
7815 //error_log('Found success transient: ' . $success_notice);
7816 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p></div>';
7817 delete_transient('mxchat_admin_notice_success');
7818 //error_log('Displayed and deleted success transient');
7819 }
7820
7821 // Check for info notices
7822 $info_notice = get_transient('mxchat_admin_notice_info');
7823 if ($info_notice) {
7824 //error_log('Found info transient: ' . $info_notice);
7825 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p></div>';
7826 delete_transient('mxchat_admin_notice_info');
7827 //error_log('Displayed and deleted info transient');
7828 }
7829
7830 // Display active processing status
7831 $this->display_processing_status();
7832 }
7833
7834
7835
7836 /**
7837 * Display current processing status
7838 */
7839 private function display_processing_status() {
7840 $pdf_url = get_transient('mxchat_last_pdf_url');
7841 $sitemap_url = get_transient('mxchat_last_sitemap_url');
7842
7843 if (!$pdf_url && !$sitemap_url) {
7844 return;
7845 }
7846
7847 $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false;
7848 $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false;
7849
7850 if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) {
7851 echo '<div class="notice notice-error is-dismissible">';
7852 echo '<p><strong>' . esc_html__('Sitemap Processing Error:', 'mxchat') . '</strong> ' . esc_html($sitemap_status['error']) . '</p>';
7853 echo '</div>';
7854 }
7855
7856 if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) {
7857 echo '<div class="notice notice-error is-dismissible">';
7858 echo '<p><strong>' . esc_html__('PDF Processing Error:', 'mxchat') . '</strong> ' . esc_html($pdf_status['error']) . '</p>';
7859 echo '</div>';
7860 }
7861 }
7862
7863
7864 }
7865 ?>
7866