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

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

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