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

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

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