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

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

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