PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.13
MxChat – AI Chatbot & Content Generation for WordPress v3.2.13
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-public.php

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

757 lines 44.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit; // Exit if accessed directly
4 }
5
6 class MxChat_Public {
7 private $options;
8
9 public function __construct() {
10 // Simply get the options without defining duplicated defaults
11 $this->options = get_option('mxchat_options', array());
12 add_shortcode('mxchat_chatbot', array($this, 'render_chatbot_shortcode'));
13 add_action('wp_footer', array($this, 'append_chatbot_to_body'));
14 }
15
16 /**
17 * UPDATED: Enhanced should_hide_chatbot method - only blocks auto-append, not shortcodes
18 */
19 private function should_hide_chatbot($context = 'auto') {
20 global $post;
21
22 if (!$post) {
23 return false;
24 }
25
26 // Only hide if it's the auto-append context (floating="yes" from global setting)
27 // Allow shortcodes to work regardless of this setting
28 if ($context === 'auto') {
29 // Check new visibility field first
30 $visibility = get_post_meta($post->ID, '_mxchat_page_visibility', true);
31 if ($visibility === 'hide') {
32 return true;
33 }
34
35 // Backward compat: check legacy field if new field not set
36 if (empty($visibility)) {
37 $hide_chatbot = get_post_meta($post->ID, '_mxchat_hide_chatbot', true);
38 if ($hide_chatbot === '1') {
39 return true;
40 }
41 }
42 }
43
44 return false;
45 }
46
47 /**
48 * UPDATED: Enhanced append_chatbot_to_body method with context
49 */
50 public function append_chatbot_to_body() {
51 // Only run on public pages
52 if (is_admin() || wp_doing_ajax()) {
53 return;
54 }
55
56 // Check if auto-append chatbot should be hidden on this page
57 if ($this->should_hide_chatbot('auto')) {
58 return; // Don't show auto-appended chatbot
59 }
60
61 // Get the bot that should be displayed using new logic
62 $bot_to_show = $this->get_display_bot();
63
64 // Don't show chatbot if determination is false
65 if ($bot_to_show === false) {
66 return;
67 }
68
69 // Handle consent for display
70 $consent_category = 'marketing';
71 $has_consent = true;
72
73 if (
74 isset($this->options['complianz_toggle']) &&
75 $this->options['complianz_toggle'] === 'on' &&
76 function_exists('cmplz_has_consent')
77 ) {
78 $has_consent = cmplz_has_consent($consent_category);
79 }
80
81 // Display the appropriate chatbot (always floating for auto-append)
82 if ($bot_to_show && $bot_to_show !== 'default') {
83 // Show specific bot
84 echo do_shortcode('[mxchat_chatbot floating="yes" bot_id="' . esc_attr($bot_to_show) . '" has_consent="' . ($has_consent ? 'yes' : 'no') . '"]');
85 } else {
86 // Show default bot
87 echo do_shortcode('[mxchat_chatbot floating="yes" has_consent="' . ($has_consent ? 'yes' : 'no') . '"]');
88 }
89 }
90
91 private function mxchat_get_user_identifier() {
92 return sanitize_text_field($_SERVER['REMOTE_ADDR']);
93 }
94
95 /**
96 * UPDATED: Enhanced shortcode with context-aware hiding
97 */
98 public function render_chatbot_shortcode($atts) {
99 // UPDATED: Add bot_id parameter support and improve logic
100 $attributes = shortcode_atts(array(
101 'floating' => 'yes',
102 'has_consent' => 'yes',
103 'bot_id' => '' // Support for multi-bot functionality - empty means auto-detect
104 ), $atts);
105
106 // Determine which bot to use
107 $bot_id = $this->determine_bot_for_shortcode($attributes['bot_id']);
108
109 // UPDATED: Only check hiding for floating shortcodes that could conflict with auto-append
110 // Non-floating shortcodes should always work
111 if ($attributes['floating'] === 'yes') {
112 // For floating shortcodes, check if auto-append is hidden
113 // This prevents duplicate floating chatbots
114 if ($this->should_hide_chatbot('auto') && $this->is_auto_append_enabled()) {
115 // If auto-append is enabled but hidden on this page,
116 // allow the floating shortcode to work (user is overriding)
117 // But if auto-append is disabled globally, also allow shortcode
118 }
119 }
120
121 // Non-floating shortcodes (floating="no") should NEVER be blocked by the hide setting
122 // This allows embedded chatbots even when floating is hidden
123
124 $is_floating = $attributes['floating'] === 'yes';
125 $bot_id = sanitize_key($bot_id); // Sanitize bot ID
126
127 // Rest of your existing shortcode rendering logic continues unchanged...
128 // [All the existing HTML generation code remains the same]
129
130 // Get bot-specific options if multi-bot add-on is active
131 $bot_options = $this->get_bot_options($bot_id);
132
133 // Use bot-specific options or fall back to default options
134 $current_options = !empty($bot_options) ? $bot_options : $this->options;
135
136 // [Rest of your existing rendering code stays exactly the same]
137 // Just remove the old should_hide_chatbot() check from the beginning
138
139 // Check for Complianz consent if the toggle is enabled
140 $initial_visibility = 'hidden';
141 $additional_class = '';
142
143 if (isset($current_options['complianz_toggle']) && $current_options['complianz_toggle'] === 'on') {
144 $additional_class = ' no-consent';
145 }
146 $visibility_class = $initial_visibility . $additional_class;
147
148 $theme_options = get_option('mxchat_theme_options', array());
149 $custom_send_image = isset($theme_options['custom_send_button_image']) ? esc_url($theme_options['custom_send_button_image']) : '';
150 $send_width = isset($theme_options['send_button_width']) ? intval($theme_options['send_button_width']) : 24;
151 $send_height = isset($theme_options['send_button_height']) ? intval($theme_options['send_button_height']) : 24;
152 $send_rotation = isset($theme_options['send_button_rotation']) ? intval($theme_options['send_button_rotation']) : 0;
153
154 // Check if an AI theme is active (global or bot-specific) - if so, skip inline color styles
155 $ai_theme_active = !empty($theme_options['active_ai_theme_css']);
156 $bot_has_theme = isset($theme_options['bot_theme_assignments'][$bot_id]);
157 $skip_inline_colors = $ai_theme_active || $bot_has_theme;
158
159 // UPDATED: Use current_options instead of $this->options throughout
160 $bg_color = $current_options['chatbot_background_color'] ?? '#fff';
161 $user_message_bg_color = $current_options['user_message_bg_color'] ?? '#fff';
162 $user_message_font_color = $current_options['user_message_font_color'] ?? '#212121';
163 $bot_message_bg_color = $current_options['bot_message_bg_color'] ?? '#212121';
164 $bot_message_font_color = $current_options['bot_message_font_color'] ?? '#fff';
165 $top_bar_bg_color = $current_options['top_bar_bg_color'] ?? '#212121';
166 $send_button_font_color = $current_options['send_button_font_color'] ?? '#212121';
167 $intro_message = $current_options['intro_message'] ?? esc_html__('Hello! How can I assist you today?', 'mxchat');
168 $top_bar_title = $current_options['top_bar_title'] ?? esc_html__('MxChat: Basic', 'mxchat');
169 $chatbot_background_color = $current_options['chatbot_background_color'] ?? '#212121';
170 $icon_color = $current_options['icon_color'] ?? '#fff';
171 $chat_input_font_color = $current_options['chat_input_font_color'] ?? '#212121';
172 $close_button_color = $current_options['close_button_color'] ?? '#fff';
173 $chatbot_bg_color = $current_options['chatbot_bg_color'] ?? '#fff';
174 $pre_chat_message = isset($current_options['pre_chat_message']) ? sanitize_textarea_field(trim($current_options['pre_chat_message'])) : '';
175 $user_id = sanitize_key($this->mxchat_get_user_identifier());
176 $email_state = $this->determine_email_collection_state();
177 $show_email_form = $email_state['show_email_form'];
178 $user_email = $email_state['user_email'] ?? '';
179 $user_name = $email_state['user_name'] ?? '';
180 // Pre-chat dismissal now handled client-side via localStorage (zero server load)
181 $input_copy = isset($current_options['input_copy']) ? esc_attr($current_options['input_copy']) : esc_attr__('How can I assist?', 'mxchat');
182 $rate_limit_message = isset($current_options['rate_limit_message']) ? esc_attr($current_options['rate_limit_message']) : esc_attr__('Rate limit exceeded. Please try again later.', 'mxchat');
183 $mode_indicator_bg_color = $current_options['mode_indicator_bg_color'] ?? '#212121';
184 $mode_indicator_font_color = $current_options['mode_indicator_font_color'] ?? '#fff';
185 $quick_questions_toggle_color = $current_options['quick_questions_toggle_color'] ?? '#212121';
186
187 $privacy_toggle = isset($current_options['privacy_toggle']) && $current_options['privacy_toggle'] === 'on';
188 $privacy_text = isset($current_options['privacy_text']) ? wp_kses_post($current_options['privacy_text']) : wp_kses_post(__('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat'));
189
190 $popular_question_1 = isset($current_options['popular_question_1']) ? esc_html($current_options['popular_question_1']) : '';
191 $popular_question_2 = isset($current_options['popular_question_2']) ? esc_html($current_options['popular_question_2']) : '';
192 $popular_question_3 = isset($current_options['popular_question_3']) ? esc_html($current_options['popular_question_3']) : '';
193 $additional_questions = isset($current_options['additional_popular_questions']) ? $current_options['additional_popular_questions'] : [];
194 $custom_icon = isset($current_options['custom_icon']) ? esc_url($current_options['custom_icon']) : '';
195 $title_icon = isset($current_options['title_icon']) ? esc_url($current_options['title_icon']) : '';
196 // AI agent text - if explicitly set to empty string, hide the indicator entirely
197 $ai_agent_text = isset($current_options['ai_agent_text']) ? $current_options['ai_agent_text'] : __('AI Agent', 'mxchat');
198
199 $live_agent_message_bg_color = $current_options['live_agent_message_bg_color'] ?? '#212121';
200 $live_agent_message_font_color = $current_options['live_agent_message_font_color'] ?? '#fff';
201 $enable_email_block = isset($current_options['enable_email_block']) &&
202 ($current_options['enable_email_block'] === '1' || $current_options['enable_email_block'] === 'on');
203
204 // Add name field variables
205 $enable_name_field = isset($current_options['enable_name_field']) &&
206 ($current_options['enable_name_field'] === '1' || $current_options['enable_name_field'] === 'on');
207 $name_field_placeholder = isset($current_options['name_field_placeholder']) ?
208 esc_attr($current_options['name_field_placeholder']) :
209 esc_attr__('Enter your name', 'mxchat');
210
211 ob_start();
212
213 // Check if floating attribute is set to 'yes' and wrap accordingly
214 if ($is_floating) {
215 echo '<div id="floating-chatbot-' . esc_attr($bot_id) . '" class="floating-chatbot ' . $initial_visibility . $additional_class . '">';
216 }
217
218 // Add bot_id to the chatbot wrapper as a data attribute
219 // data-nosnippet: keep the chat widget's UI copy (greeting, title, quick-question
220 // prompts, privacy notice) out of Google search snippets. This wrapper renders in both
221 // floating and inline/shortcode modes, so it covers all in-panel copy in one place.
222 echo '<div id="mxchat-chatbot-wrapper-' . esc_attr($bot_id) . '" class="mxchat-chatbot-wrapper" data-nosnippet data-bot-id="' . esc_attr($bot_id) . '">';
223
224 echo ' <div class="chatbot-top-bar" id="exit-chat-button-' . esc_attr($bot_id) . '"' . ($skip_inline_colors ? '' : ' style="background: ' . esc_attr($top_bar_bg_color) . ';"') . '>';
225 echo ' <div class="chatbot-title-container">';
226 echo ' <div class="chatbot-title-group">';
227 if (!empty($title_icon)) {
228 echo ' <img src="' . esc_url($title_icon) . '" alt="" class="chatbot-title-icon">';
229 }
230 echo ' <p class="chatbot-title"' . ($skip_inline_colors ? '' : ' style="color: ' . esc_attr($close_button_color) . ';"') . '>' . esc_html($top_bar_title) . '</p>';
231 echo ' </div>';
232 // Only show mode indicator if ai_agent_text is not empty
233 if (!empty(trim($ai_agent_text))) {
234 echo '<span class="chat-mode-indicator" id="chat-mode-indicator-' . esc_attr($bot_id) . '" data-ai-text="' . esc_attr($ai_agent_text) . '"' . ($skip_inline_colors ? '' : ' style="color: ' . esc_attr($mode_indicator_font_color) . '; background-color: ' . esc_attr($mode_indicator_bg_color) . ';"') . '>' . esc_html($ai_agent_text) . '</span>';
235 }
236 echo ' </div>';
237 // Overflow menu (3-dot) trigger + dropdown container.
238 // Sibling of .exit-chat, rendered to its left. JS hides the trigger
239 // if no menu items are enabled; outer click + Escape close the menu.
240 echo ' <div class="mxchat-header-menu-wrap" data-bot-id="' . esc_attr($bot_id) . '"' . ($skip_inline_colors ? '' : ' style="--mxchat-menu-bg: ' . esc_attr($bot_message_bg_color) . '; --mxchat-menu-fg: ' . esc_attr($bot_message_font_color) . ';"') . '>';
241 echo ' <button class="mxchat-header-btn mxchat-menu-trigger" type="button" aria-haspopup="menu" aria-expanded="false" aria-label="' . esc_attr__('More options', 'mxchat') . '"' . ($skip_inline_colors ? '' : ' style="color: ' . esc_attr($close_button_color) . ';"') . '>';
242 echo ' <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">';
243 echo ' <circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/>';
244 echo ' </svg>';
245 echo ' </button>';
246 echo ' <div class="mxchat-header-menu" role="menu" aria-hidden="true" hidden></div>';
247 echo ' </div>';
248 echo ' <button class="exit-chat" type="button" aria-label="' . esc_attr__('Close', 'mxchat') . '"' . ($skip_inline_colors ? '' : ' style="color: ' . esc_attr($close_button_color) . ';"') . '>';
249 echo ' <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" id="ic-close" aria-hidden="true">';
250 echo ' <line x1="6" y1="6" x2="18" y2="18"></line>';
251 echo ' <line x1="18" y1="6" x2="6" y2="18"></line>';
252 echo ' </svg>';
253 echo ' <span>' . esc_html__('Close', 'mxchat') . '</span>';
254 echo ' </button>';
255 echo ' </div>';
256
257 // 3b) Main chatbot container
258 echo ' <div id="mxchat-chatbot-' . esc_attr($bot_id) . '" class="mxchat-chatbot"' . ($skip_inline_colors ? '' : ' style="background-color: ' . esc_attr($chatbot_bg_color) . ';"') . '>';
259
260 if ($enable_email_block) {
261 echo '<div id="email-blocker-' . esc_attr($bot_id) . '" class="email-blocker" style="' . ($show_email_form ? '' : 'display: none;') . '">';
262 echo ' <div class="email-blocker-header">';
263
264 $header_html = isset($current_options['email_blocker_header_content'])
265 ? $current_options['email_blocker_header_content']
266 : '';
267 echo wp_kses_post($header_html);
268
269 echo ' </div>';
270 echo ' <form id="email-collection-form-' . esc_attr($bot_id) . '" class="email-collection-form" method="POST" action="">';
271
272 // Add name field if enabled
273 if ($enable_name_field) {
274 echo ' <label for="user-name-' . esc_attr($bot_id) . '" class="sr-only mxchat-name-label">' . esc_html__('Name', 'mxchat') . '</label>';
275 echo ' <input type="text" id="user-name-' . esc_attr($bot_id) . '" name="user_name" class="mxchat-name-input" required placeholder="' . $name_field_placeholder . '" />';
276 }
277
278 echo ' <label for="user-email-' . esc_attr($bot_id) . '" class="sr-only">' . esc_html__('Email Address', 'mxchat') . '</label>';
279 echo ' <input type="email" id="user-email-' . esc_attr($bot_id) . '" name="user_email" class="mxchat-email-input" required placeholder="' . esc_attr__('Enter your email address', 'mxchat') . '" />';
280 echo '<button type="submit" id="email-submit-button-' . esc_attr($bot_id) . '" class="email-submit-button">';
281 $button_text = isset($current_options['email_blocker_button_text'])
282 ? $current_options['email_blocker_button_text']
283 : esc_html__('Start Chat', 'mxchat');
284 echo esc_html($button_text);
285 echo '</button>';
286 echo ' </form>';
287 echo '</div>';
288 }
289
290 echo ' <div id="mxchat-init-loader-' . esc_attr($bot_id) . '" class="mxchat-init-loader" style="display:none;">';
291 echo ' <div class="mxchat-init-loader-dots">';
292 echo ' <span class="dot"></span><span class="dot"></span><span class="dot"></span>';
293 echo ' </div>';
294 echo ' </div>';
295
296 echo ' <div id="chat-container-' . esc_attr($bot_id) . '" class="chat-container" style="' . ($enable_email_block && $show_email_form ? 'display: none;' : '') . '">';
297 echo ' <div id="chat-box-' . esc_attr($bot_id) . '" class="chat-box">';
298 echo ' <div class="bot-message"' . ($skip_inline_colors ? '' : ' style="background: ' . esc_attr($bot_message_bg_color) . ';"') . '>';
299 echo ' <div dir="auto"' . ($skip_inline_colors ? '' : ' style="color: ' . esc_attr($bot_message_font_color) . ';"') . '>';
300 echo wp_kses_post($intro_message);
301 echo ' </div>';
302 echo ' </div>';
303 echo ' </div>'; // end #chat-box
304
305
306 // Replace the existing popular questions section with this:
307 echo ' <div id="mxchat-popular-questions-' . esc_attr($bot_id) . '" class="mxchat-popular-questions">';
308 echo ' <div class="mxchat-popular-questions-container">';
309
310 // Collapse button (down arrow) - shows when open, centered at top
311 echo ' <button class="questions-collapse-btn" aria-label="' . esc_attr__('Hide Quick Questions', 'mxchat') . '">';
312 echo ' <svg width="25" height="25" viewBox="0 0 24 24" fill="none"' . ($skip_inline_colors ? '' : ' stroke="' . esc_attr($quick_questions_toggle_color) . '"') . ' stroke-width="2">';
313 echo ' <polyline points="6,9 12,15 18,9"></polyline>';
314 echo ' </svg>';
315 echo ' </button>';
316
317 // Expand button (up arrow) - shows when collapsed
318 echo ' <button class="questions-toggle-btn" aria-label="' . esc_attr__('Show Quick Questions', 'mxchat') . '">';
319 echo ' <svg width="25" height="25" viewBox="0 0 24 24" fill="none"' . ($skip_inline_colors ? '' : ' stroke="' . esc_attr($quick_questions_toggle_color) . '"') . ' stroke-width="2">';
320 echo ' <polyline points="18,15 12,9 6,15"></polyline>';
321 echo ' </svg>';
322 echo ' </button>';
323
324 if (!empty($popular_question_1)) {
325 echo '<button class="mxchat-popular-question" dir="auto">' . esc_html($popular_question_1) . '</button>';
326 }
327 if (!empty($popular_question_2)) {
328 echo '<button class="mxchat-popular-question" dir="auto">' . esc_html($popular_question_2) . '</button>';
329 }
330 if (!empty($popular_question_3)) {
331 echo '<button class="mxchat-popular-question" dir="auto">' . esc_html($popular_question_3) . '</button>';
332 }
333
334 if (!empty($additional_questions) && is_array($additional_questions)) {
335 foreach ($additional_questions as $index => $question) {
336 if (!empty($question)) {
337 echo '<button class="mxchat-popular-question" dir="auto">' . esc_html($question) . '</button>';
338 }
339 }
340 }
341
342 echo ' </div>';
343 echo ' </div>';
344
345 echo ' <div id="input-container-' . esc_attr($bot_id) . '" class="input-container">';
346 // Max input length (plan a3fae2 part C) — global core setting, 0 = unlimited.
347 // Hard-caps typing/paste client-side; the chat handler enforces it server-side too.
348 $mxchat_max_input_length = isset($this->options['max_input_length']) ? intval($this->options['max_input_length']) : 0;
349 $mxchat_maxlength_attr = $mxchat_max_input_length > 0 ? ' maxlength="' . esc_attr($mxchat_max_input_length) . '"' : '';
350 echo ' <textarea id="chat-input-' . esc_attr($bot_id) . '" class="chat-input" dir="auto"' . $mxchat_maxlength_attr . ' placeholder="' . esc_attr($input_copy) . '"' . ($skip_inline_colors ? '' : ' style="color: ' . esc_attr($chat_input_font_color) . ';"') . '></textarea>';
351 // Language-neutral character counter (plan 7091a2). Numbers only — no
352 // translatable strings — so it reads correctly on every-language install.
353 // Only rendered when a cap is set; hidden until ~80% of the cap, then
354 // ramps neutral -> amber -> red. Decorative (aria-hidden); the textarea's
355 // maxlength carries the real semantics for assistive tech.
356 if ($mxchat_max_input_length > 0) {
357 echo ' <div id="mxchat-char-counter-' . esc_attr($bot_id) . '" class="mxchat-char-counter" aria-hidden="true"><span class="mxchat-char-counter-current">0</span><span class="mxchat-char-counter-sep">/</span><span class="mxchat-char-counter-max">' . esc_html($mxchat_max_input_length) . '</span></div>';
358 }
359 echo ' <button id="send-button-' . esc_attr($bot_id) . '" class="send-button" aria-label="' . esc_attr__('Send message', 'mxchat') . '">';
360 if (!empty($custom_send_image)) {
361 echo ' <img src="' . esc_url($custom_send_image) . '" alt="' . esc_attr__('Send', 'mxchat') . '" style="width: ' . intval($send_width) . 'px; height: ' . intval($send_height) . 'px; transform: rotate(' . intval($send_rotation) . 'deg);" />';
362 } else {
363 $send_svg_style = 'width: ' . intval($send_width) . 'px; height: ' . intval($send_height) . 'px; transform: rotate(' . intval($send_rotation) . 'deg);';
364 if (!$skip_inline_colors) {
365 $send_svg_style = 'fill: ' . esc_attr($send_button_font_color) . '; ' . $send_svg_style;
366 }
367 echo ' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="' . $send_svg_style . '">';
368 echo ' <path d="M498.1 5.6c10.1 7 15.4 19.1 13.5 31.2l-64 416c-1.5 9.7-7.4 18.2-16 23s-18.9 5.4-28 1.6L284 427.7l-68.5 74.1c-8.9 9.7-22.9 12.9-35.2 8.1S160 493.2 160 480V396.4c0-4 1.5-7.8 4.2-10.7L331.8 202.8c5.8-6.3 5.6-16-.4-22s-15.7-6.4-22-.7L106 360.8 17.7 316.6C7.1 311.3 .3 300.7 0 288.9s5.9-22.8 16.1-28.7l448-256c10.7-6.1 23.9-5.5 34 1.4z"></path>';
369 echo ' </svg>';
370 }
371 echo ' </button>';
372 echo ' </div>';
373
374 echo ' <div class="chat-toolbar">';
375
376 // PDF Upload Button - wrapped in conditional using current_options
377 $show_pdf_button = isset($current_options['show_pdf_upload_button']) ? $current_options['show_pdf_upload_button'] : 'on';
378 if ($show_pdf_button === 'on') {
379 echo ' <input type="file" id="pdf-upload-' . esc_attr($bot_id) . '" class="pdf-upload" accept=".pdf" style="display: none;">';
380 echo ' <button id="pdf-upload-btn-' . esc_attr($bot_id) . '" class="toolbar-btn pdf-upload-btn" title="' . esc_attr__('Upload PDF', 'mxchat') . '">';
381 echo ' <!-- Icon from Font Awesome Free: https://fontawesome.com/license/free -->';
382 echo ' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" stroke="currentColor">';
383 echo ' <path d="M64 464l48 0 0 48-48 0c-35.3 0-64-28.7-64-64L0 64C0 28.7 28.7 0 64 0L229.5 0c17 0 33.3 6.7 45.3 18.7l90.5 90.5c12 12 18.7 28.3 18.7 45.3L384 304l-48 0 0-144-80 0c-17.7 0-32-14.3-32-32l0-80L64 48c-8.8 0-16 7.2-16 16l0 384c0 8.8 7.2 16 16 16zM176 352l32 0c30.9 0 56 25.1 56 56s-25.1 56-56 56l-16 0 0 32c0 8.8-7.2 16-16 16s-16-7.2-16-16l0-48 0-80c0-8.8 7.2-16 16-16zm32 80c13.3 0 24-10.7 24-24s-10.7-24-24-24l-16 0 0 48 16 0zm96-80l32 0c26.5 0 48 21.5 48 48l0 64c0 26.5-21.5 48-48 48l-32 0c-8.8 0-16-7.2-16-16l0-128c0-8.8 7.2-16 16-16zm32 128c8.8 0 16-7.2 16-16l0-64c0-8.8-7.2-16-16-16l-16 0 0 96 16 0zm80-112c0-8.8 7.2-16 16-16l48 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-32 0 0 32 32 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-32 0 0 48c0 8.8-7.2 16-16 16s-16-7.2-16-16l0-64 0-64z"></path>';
384 echo ' </svg>';
385 echo ' </button>';
386 }
387
388 // Word Upload Button - wrapped in conditional using current_options
389 $show_word_button = isset($current_options['show_word_upload_button']) ? $current_options['show_word_upload_button'] : 'on';
390 if ($show_word_button === 'on') {
391 echo ' <input type="file" id="word-upload-' . esc_attr($bot_id) . '" class="word-upload" accept=".docx" style="display: none;">';
392 echo ' <button id="word-upload-btn-' . esc_attr($bot_id) . '" class="toolbar-btn word-upload-btn" title="' . esc_attr__('Upload Word Document', 'mxchat') . '">';
393 echo ' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" stroke="currentColor">';
394 echo ' <path d="M48 448L48 64c0-8.8 7.2-16 16-16l160 0 0 80c0 17.7 14.3 32 32 32l80 0 0 288c0 8.8-7.2 16-16 16L64 464c-8.8 0-16-7.2-16-16zM64 0C28.7 0 0 28.7 0 64L0 448c0 35.3 28.7 64 64 64l256 0c35.3 0 64-28.7 64-64l0-293.5c0-17-6.7-33.3-18.7-45.3L274.7 18.7C262.7 6.7 246.5 0 229.5 0L64 0zm55 241.1c-3.8-12.7-17.2-19.9-29.9-16.1s-19.9 17.2-16.1 29.9l48 160c3 10.2 12.4 17.1 23 17.1s19.9-7 23-17.1l25-83.4 25 83.4c3 10.2 12.4 17.1 23 17.1s19.9-7 23-17.1l48-160c3.8-12.7-3.4-26.1-16.1-29.9s-26.1 3.4-29.9 16.1l-25 83.4-25-83.4c-3-10.2-12.4-17.1-23-17.1s-19.9 7-23 17.1l-25 83.4-25-83.4z"/></svg>';
395 echo ' </button>';
396 }
397
398 // File containers
399 echo ' <div id="active-pdf-container-' . esc_attr($bot_id) . '" class="active-pdf-container" style="display: none;">';
400 echo ' <span id="active-pdf-name-' . esc_attr($bot_id) . '" class="active-pdf-name"></span>';
401 echo ' <button id="remove-pdf-btn-' . esc_attr($bot_id) . '" class="remove-pdf-btn" title="' . esc_attr__('Remove PDF', 'mxchat') . '">';
402 echo ' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2">';
403 echo ' <line x1="18" y1="6" x2="6" y2="18"></line>';
404 echo ' <line x1="6" y1="6" x2="18" y2="18"></line>';
405 echo ' </svg>';
406 echo ' </button>';
407 echo ' </div>';
408
409 echo ' <div id="active-word-container-' . esc_attr($bot_id) . '" class="active-word-container" style="display: none;">';
410 echo ' <span id="active-word-name-' . esc_attr($bot_id) . '" class="active-word-name"></span>';
411 echo ' <button id="remove-word-btn-' . esc_attr($bot_id) . '" class="remove-word-btn" title="' . esc_attr__('Remove Word Document', 'mxchat') . '">';
412 echo ' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2">';
413 echo ' <line x1="18" y1="6" x2="6" y2="18"></line>';
414 echo ' <line x1="6" y1="6" x2="18" y2="18"></line>';
415 echo ' </svg>';
416 echo ' </button>';
417 echo ' </div>';
418
419 // Perplexity Button
420 if (apply_filters('mxchat_perplexity_should_show_logo', true)) {
421 echo ' <button id="perplexity-search-btn-' . esc_attr($bot_id) . '" class="toolbar-btn perplexity-search-btn" title="' . esc_attr__('Search with Perplexity', 'mxchat-perplexity') . '">';
422 echo ' <svg fill="currentColor" height="1em" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg">';
423 echo ' <path d="M19.785 0v7.272H22.5V17.62h-2.935V24l-7.037-6.194v6.145h-1.091v-6.152L4.392 24v-6.465H1.5V7.188h2.884V0l7.053 6.494V.19h1.09v6.49L19.786 0zm-7.257 9.044v7.319l5.946 5.234V14.44l-5.946-5.397zm-1.099-.08l-5.946 5.398v7.235l5.946-5.234V8.965zm8.136 7.58h1.844V8.349H13.46l6.105 5.54v2.655zm-8.982-8.28H2.59v8.195h1.8v-2.576l6.192-5.62zM5.475 2.476v4.71h5.115l-5.115-4.71zm13.219 0l-5.115 4.71h5.115v-4.71z"></path>';
424 echo ' </svg>';
425 echo ' </button>';
426 }
427
428
429 // Image Analysis Button - hidden by default, controlled by MxChat Vision add-on
430 echo ' <input type="file" id="image-upload-' . esc_attr($bot_id) . '" class="image-upload" accept="image/*" style="display: none;" multiple>';
431 echo ' <button id="image-upload-btn-' . esc_attr($bot_id) . '" class="toolbar-btn image-upload-btn" title="' . esc_attr__('Upload Image for Analysis', 'mxchat') . '" style="display: none;">';
432 echo ' <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="fill: none !important;">';
433 echo ' <path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" style="fill: none !important;"/>';
434 echo ' <circle cx="12" cy="13" r="3" style="fill: none !important;"/>';
435 echo ' </svg>';
436 echo ' </button>';
437
438
439 echo ' </div>';
440
441 echo ' <div class="chatbot-footer">';
442
443 // Output the privacy notice if enabled
444 if ($privacy_toggle && !empty($privacy_text)) {
445 echo '<p class="privacy-notice">' . $privacy_text . '</p>';
446 }
447
448 echo ' </div>';
449 echo ' </div>';
450 echo ' </div>';
451 echo '</div>';
452
453 if ($is_floating) {
454 echo '</div>';
455
456 if (!empty($pre_chat_message)) {
457 // Rendered hidden by default — JS checkPreChatDismissal() handles show/hide via localStorage
458 // data-nosnippet: the pre-chat teaser bubble is a sibling outside
459 // .mxchat-chatbot-wrapper, so it needs its own marker to stay out of snippets.
460 echo '<div id="pre-chat-message-' . esc_attr($bot_id) . '" class="pre-chat-message" data-nosnippet style="display:none;">';
461 echo nl2br(esc_html($pre_chat_message));
462 echo '<button class="close-pre-chat-message" aria-label="' . esc_attr__('Close', 'mxchat') . '">&times;</button>';
463 echo '</div>';
464 }
465
466 $is_initially_hidden = (strpos($visibility_class, 'hidden') !== false);
467 $aria_expanded = $is_initially_hidden ? 'false' : 'true';
468 echo '<div class="floating-chatbot-button ' . esc_attr($visibility_class) . '" id="floating-chatbot-button-' . esc_attr($bot_id) . '" role="button" tabindex="0" aria-label="' . esc_attr__('Open chat', 'mxchat') . '" aria-expanded="' . esc_attr($aria_expanded) . '" aria-controls="floating-chatbot-' . esc_attr($bot_id) . '"' . ($skip_inline_colors ? '' : ' style="background: ' . esc_attr($chatbot_background_color) . '; color: ' . esc_attr($send_button_font_color) . ';"') . '>';
469 echo '<div id="chat-notification-badge-' . esc_attr($bot_id) . '" class="chat-notification-badge" style="display: none; position: absolute; top: -8px; right: -8px; background-color: #ff4444; color: white; border-radius: 50%; padding: 4px 8px; font-size: 12px; font-weight: bold; z-index: 10001;">1</div>';
470
471 if (!empty($custom_icon)) {
472 echo '<img src="' . $custom_icon . '" alt="' . esc_attr__('Chatbot Icon', 'mxchat') . '" style="height: 48px; width: 48px; object-fit: contain;" />';
473 } else {
474 $widget_icon_style = 'height: 48px; width: 48px;';
475 if (!$skip_inline_colors) {
476 $widget_icon_style .= ' fill: ' . esc_attr($icon_color) . ';';
477 }
478 echo '<svg id="widget_icon_10" style="' . $widget_icon_style . '" viewBox="0 0 1120 1120" fill="none" xmlns="http://www.w3.org/2000/svg">';
479 echo ' <path fill-rule="evenodd" clip-rule="evenodd" d="M252 434C252 372.144 302.144 322 364 322H770C831.856 322 882 372.144 882 434V614.459L804.595 585.816C802.551 585.06 800.94 583.449 800.184 581.405L763.003 480.924C760.597 474.424 751.403 474.424 748.997 480.924L711.816 581.405C711.06 583.449 709.449 585.06 707.405 585.816L606.924 622.997C600.424 625.403 600.424 634.597 606.924 637.003L707.405 674.184C709.449 674.94 711.06 676.551 711.816 678.595L740.459 756H629.927C629.648 756.476 629.337 756.945 628.993 757.404L578.197 825.082C572.597 832.543 561.403 832.543 555.803 825.082L505.007 757.404C504.663 756.945 504.352 756.476 504.073 756H364C302.144 756 252 705.856 252 644V434ZM633.501 471.462C632.299 468.212 627.701 468.212 626.499 471.462L619.252 491.046C618.874 492.068 618.068 492.874 617.046 493.252L597.462 500.499C594.212 501.701 594.212 506.299 597.462 507.501L617.046 514.748C618.068 515.126 618.874 515.932 619.252 516.954L626.499 536.538C627.701 539.788 632.299 539.788 633.501 536.538L640.748 516.954C641.126 515.932 641.932 515.126 642.954 514.748L662.538 507.501C665.788 506.299 665.788 501.701 662.538 500.499L642.954 493.252C641.932 492.874 641.126 492.068 640.748 491.046L633.501 471.462Z" ></path>';
480 echo ' <path d="M771.545 755.99C832.175 755.17 881.17 706.175 881.99 645.545L804.595 674.184C802.551 674.94 800.94 676.551 800.184 678.595L771.545 755.99Z" ></path>';
481 echo '</svg>';
482 }
483 echo '</div>';
484 }
485
486 return ob_get_clean();
487 }
488
489 /**
490 * Get bot-specific options for multi-bot functionality
491 * Falls back to default options if bot_id is 'default' or multi-bot add-on is not active
492 */
493 private function get_bot_options($bot_id = 'default') {
494 // If default bot or multi-bot add-on not active, return empty (use default options)
495 if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
496 return array();
497 }
498
499 //Hook for multi-bot add-on to provide bot-specific options
500 $bot_options = apply_filters('mxchat_get_bot_options', array(), $bot_id);
501
502 return is_array($bot_options) ? $bot_options : array();
503 }
504
505 /**
506 * Get bot-specific Pinecone configuration
507 * Used in the knowledge retrieval functions
508 */
509 private function get_bot_pinecone_config($bot_id = 'default') {
510 // If default bot or multi-bot add-on not active, use default Pinecone config
511 if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
512 $addon_options = get_option('mxchat_pinecone_addon_options', array());
513 return array(
514 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
515 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
516 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
517 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
518 );
519 }
520
521 // Hook for multi-bot add-on to provide bot-specific Pinecone config
522 $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
523
524 return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
525 }
526
527
528 private function determine_email_collection_state() {
529 // Logged-in users skip the email form
530 if (is_user_logged_in()) {
531 $current_user = wp_get_current_user();
532 return [
533 'show_email_form' => false,
534 'user_email' => $current_user->user_email,
535 'user_name' => $current_user->display_name ?: $current_user->first_name ?: ''
536 ];
537 }
538
539 // For guests, default to showing the email form.
540 // The session-based check happens client-side via AJAX since the
541 // session ID lives in the browser cookie/localStorage.
542 return [
543 'show_email_form' => true,
544 'user_email' => '',
545 'user_name' => ''
546 ];
547 }
548
549
550 /**
551 * NEW: Determine if and which chatbot should be displayed
552 */
553 private function get_display_bot() {
554 // Get page-specific settings from meta box
555 $page_setting = $this->get_page_bot_setting();
556
557 // Get global settings - FIXED: Check for 'on' instead of 'on'
558 $global_autoshow = isset($this->options['append_to_body']) && $this->options['append_to_body'] === 'on';
559 $global_default_bot = isset($this->options['default_bot']) ? $this->options['default_bot'] : 'default';
560
561 // If page specifically hides chatbot, don't show anything
562 if ($page_setting && $page_setting['action'] === 'hide') {
563 return false;
564 }
565
566 // If page specifies a specific bot, use that
567 if ($page_setting && $page_setting['action'] === 'show') {
568 return $page_setting['bot_id'];
569 }
570
571 // Page setting is 'global' or no page setting exists
572 // Check global auto-show setting
573 if ($global_autoshow) {
574 // Check post type visibility settings
575 if (!$this->should_show_on_current_post_type()) {
576 return false;
577 }
578
579 // Global auto-show is enabled, return the default bot
580 return $global_default_bot;
581 }
582
583 // Global auto-show is disabled and no page-specific bot selected
584 // Don't show chatbot (user should use shortcodes)
585 return false;
586 }
587
588 /**
589 * Check if chatbot should be shown on the current post type
590 */
591 private function should_show_on_current_post_type() {
592 // Get visibility settings
593 $mode = isset($this->options['post_type_visibility_mode']) ? $this->options['post_type_visibility_mode'] : 'all';
594 $list = isset($this->options['post_type_visibility_list']) ? $this->options['post_type_visibility_list'] : array();
595
596 // Ensure list is an array
597 if (!is_array($list)) {
598 $list = array();
599 }
600
601 // If mode is 'all', show on all post types
602 if ($mode === 'all') {
603 return true;
604 }
605
606 // Get current post type
607 $current_post_type = $this->get_current_post_type();
608
609 // If we can't determine post type, default to showing
610 if (empty($current_post_type)) {
611 return true;
612 }
613
614 // Check based on mode
615 if ($mode === 'include') {
616 // Only show on selected post types
617 return in_array($current_post_type, $list);
618 } elseif ($mode === 'exclude') {
619 // Hide on selected post types
620 return !in_array($current_post_type, $list);
621 }
622
623 // Default to showing
624 return true;
625 }
626
627 /**
628 * Get the current post type
629 */
630 private function get_current_post_type() {
631 // Try to get from queried object first
632 $queried_object = get_queried_object();
633
634 if ($queried_object instanceof WP_Post) {
635 return $queried_object->post_type;
636 }
637
638 // Try get_post_type()
639 $post_type = get_post_type();
640 if ($post_type) {
641 return $post_type;
642 }
643
644 // Check if we're on an archive
645 if (is_post_type_archive()) {
646 return get_query_var('post_type');
647 }
648
649 // Check common archive types
650 if (is_home() || is_single()) {
651 return 'post';
652 }
653
654 if (is_page()) {
655 return 'page';
656 }
657
658 return '';
659 }
660
661 /**
662 * Get page-specific bot setting using new visibility field with backward compat
663 */
664 private function get_page_bot_setting($post_id = null) {
665 if (!$post_id) {
666 $post_id = get_the_ID();
667 }
668
669 if (!$post_id) {
670 return null;
671 }
672
673 // Check new visibility field first
674 $visibility = get_post_meta($post_id, '_mxchat_page_visibility', true);
675
676 if ($visibility === 'hide') {
677 return array('action' => 'hide');
678 }
679
680 if ($visibility === 'show') {
681 $selected_bot = get_post_meta($post_id, '_mxchat_selected_bot', true);
682 $bot_id = !empty($selected_bot) ? $selected_bot : 'default';
683 return array('action' => 'show', 'bot_id' => $bot_id);
684 }
685
686 // Backward compat: check legacy hide checkbox if no new field set
687 if (empty($visibility)) {
688 $hide_chatbot = get_post_meta($post_id, '_mxchat_hide_chatbot', true);
689 if ($hide_chatbot === '1') {
690 return array('action' => 'hide');
691 }
692 }
693
694 // Check if specific bot is selected (legacy path)
695 $selected_bot = get_post_meta($post_id, '_mxchat_selected_bot', true);
696 if (!empty($selected_bot)) {
697 return array('action' => 'show', 'bot_id' => $selected_bot);
698 }
699
700 // Use global setting
701 return array('action' => 'global');
702 }
703
704 /**
705 * NEW: Determine which bot to use for shortcode
706 */
707 private function determine_bot_for_shortcode($shortcode_bot_id) {
708 // If bot_id explicitly provided in shortcode, use that
709 if (!empty($shortcode_bot_id)) {
710 return $shortcode_bot_id;
711 }
712
713 // No bot_id in shortcode, check page setting
714 $page_setting = $this->get_page_bot_setting();
715 if ($page_setting && $page_setting['action'] === 'show') {
716 return $page_setting['bot_id'];
717 }
718
719 // Fall back to default
720 return 'default';
721 }
722
723 /**
724 * NEW: Helper to check if auto-append is enabled globally
725 */
726 private function is_auto_append_enabled() {
727 return isset($this->options['append_to_body']) && $this->options['append_to_body'] === 'on';
728 }
729
730 /**
731 * UPDATED: Debug function with new context info
732 */
733
734 /**
735 * NEW: Helper method to get available bots (for admin notices, etc.)
736 */
737 private function get_available_bots() {
738 $bots = array('default' => __('Default Bot', 'mxchat'));
739
740 // Check if multi-bot addon is active
741 if (class_exists('MxChat_Multi_Bot_Manager')) {
742 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
743 $available_bots = $multi_bot_manager->get_available_bots();
744
745 // Add the available bots
746 foreach ($available_bots as $bot_id => $bot_name) {
747 $bots[$bot_id] = $bot_name;
748 }
749 }
750
751 return $bots;
752 }
753
754
755
756 }
757 ?>