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

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

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