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

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

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