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

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