options = get_option('mxchat_options'); $this->chat_count = get_option('mxchat_chat_count', 0); $this->is_activated = $this->is_license_active(); // Initialize default options if they are not set if (!$this->options) { $this->initialize_default_options(); } // Add admin menu and initialize settings add_action('admin_menu', array($this, 'mxchat_add_plugin_page')); add_action('admin_init', array($this, 'mxchat_page_init')); add_action('admin_init', array($this, 'mxchat_prompts_page_init')); add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets')); add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history')); add_action('admin_post_mxchat_submit_content', array($this, 'mxchat_handle_content_submission')); add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt')); add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history')); add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history')); add_action('admin_post_mxchat_submit_sitemap', array($this, 'mxchat_handle_sitemap_submission')); add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body')); add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets')); add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets')); add_action('wp_ajax_mxchat_activate_license', array($this, 'mxchat_handle_activate_license')); add_action('admin_notices', array($this, 'mxchat_display_admin_notice')); add_action('wp_ajax_mxchat_save_inline_prompt', array($this, 'mxchat_save_inline_prompt')); add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts')); add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent')); add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent')); add_action('wp_ajax_mxchat_toggle_action', array($this, 'mxchat_toggle_action')); add_action('wp_ajax_mxchat_update_intent_threshold', array($this, 'mxchat_update_intent_threshold')); add_action('admin_post_mxchat_stop_processing', array($this, 'mxchat_stop_processing')); add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent')); add_action('save_post', array($this, 'handle_post_update'), 10, 3); add_action('post_updated', array($this, 'handle_post_update'), 10, 3); add_action('wp_ajax_mxchat_save_setting', array($this, 'mxchat_save_setting_callback')); add_action('wp_trash_post', array($this, 'mxchat_handle_post_delete')); add_action('before_delete_post', array($this, 'mxchat_handle_post_delete')); add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts')); add_action('wp_ajax_mxchat_save_prompts_setting', array($this, 'mxchat_save_prompts_setting_callback')); if (isset($this->options['enable_woocommerce_integration']) && ($this->options['enable_woocommerce_integration'] === '1' || $this->options['enable_woocommerce_integration'] === 'on')) { //error_log('MxChat Admin: WooCommerce integration is enabled, adding hooks'); add_action('wp_insert_post', array($this, 'mxchat_handle_product_change'), 10, 3); add_action('wp_trash_post', array($this, 'mxchat_handle_product_delete')); add_action('before_delete_post', array($this, 'mxchat_handle_product_delete')); } add_action('wp_ajax_mxchat_get_status_updates', array($this, 'ajax_get_status_updates')); add_action('admin_notices', array($this, 'display_admin_notices')); } // Method to check if the license is active private function is_license_active() { $license_status = get_option('mxchat_license_status', esc_html__('inactive', 'mxchat')); return $license_status === esc_html__('active', 'mxchat'); } // Initialize default options private function initialize_default_options() { $default_options = array( 'api_key' => '', 'xai_api_key' => '', 'claude_api_key' => '', 'deepseek_api_key' => '', 'voyage_api_key' => '', 'gemini_api_key' => '', 'embedding_model' => 'text-embedding-ada-002', '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: - Your name is [Chatbot Name]. - 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. - 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. - Keep your responses short, concise, and to the point. Provide clear and direct answers suitable for a chatbot interaction. - 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'), 'model' => esc_html__('gpt-4o', 'mxchat'), 'rate_limit_logged_out' => esc_html__('100', 'mxchat'), 'role_rate_limits' => array(), 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'), 'enable_email_block' => '', 'email_blocker_header_content' => __("

Welcome to Our Chat!

\n

Let's get started. Enter your email to begin chatting with us.

", 'mxchat'), 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'), 'top_bar_title' => esc_html__('MxChat', 'mxchat'), 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'), 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'), 'input_copy' => esc_html__('How can I assist?', 'mxchat'), 'append_to_body' => esc_html__('off', 'mxchat'), 'close_button_color' => esc_html__('#fff', 'mxchat'), 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'), 'user_message_bg_color' => esc_html__('#fff', 'mxchat'), 'user_message_font_color' => esc_html__('#212121', 'mxchat'), 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'), 'bot_message_font_color' => esc_html__('#fff', 'mxchat'), 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'), 'send_button_font_color' => esc_html__('#212121', 'mxchat'), 'chat_input_font_color' => esc_html__('#212121', 'mxchat'), 'chatbot_background_color' => esc_html__('#212121', 'mxchat'), 'icon_color' => esc_html__('#fff', 'mxchat'), 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'), 'link_target_toggle' => esc_html__('off', 'mxchat'), 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'), // New fields for Loops Integration 'loops_api_key' => '', 'loops_mailing_list' => '', 'triggered_phrase_response' => esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'), 'email_capture_response' => esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'), 'popular_question_1' => '', 'popular_question_2' => '', 'popular_question_3' => '', 'pdf_intent_trigger_text' => esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'), 'pdf_intent_success_text' => esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'), 'pdf_intent_error_text' => esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'), 'pdf_max_pages' => 69, 'show_pdf_upload_button' => 'on', 'show_word_upload_button' => 'on', // Live Agent Integration 'live_agent_webhook_url' => '', 'live_agent_secret_key' => '', 'live_agent_bot_token' => '', 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'), 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'), 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'), 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'), 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'), 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'), ); // Merge existing options with defaults $existing_options = get_option('mxchat_options', array()); $merged_options = wp_parse_args($existing_options, $default_options); // Update the options if they have changed if ($existing_options !== $merged_options) { update_option('mxchat_options', $merged_options); } // Add default limits for each role $roles = wp_roles()->get_names(); foreach ($roles as $role_id => $role_name) { $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat'); } return $default_options; // Update the $this->options property $this->options = $merged_options; } public function mxchat_add_plugin_page() { // Main menu page add_menu_page( esc_html__('MxChat Settings', 'mxchat'), esc_html__('MxChat', 'mxchat'), 'manage_options', 'mxchat-max', array($this, 'mxchat_create_admin_page'), 'dashicons-testimonial', 6 ); // Submenu page for Knowledge add_submenu_page( 'mxchat-max', esc_html__('Prompts', 'mxchat'), esc_html__('Knowledge', 'mxchat'), 'manage_options', 'mxchat-prompts', array($this, 'mxchat_create_prompts_page') ); add_submenu_page( 'mxchat-max', esc_html__('Chat Transcripts', 'mxchat'), esc_html__('Transcripts', 'mxchat'), 'manage_options', 'mxchat-transcripts', array($this, 'mxchat_create_transcripts_page') ); add_submenu_page( 'mxchat-max', esc_html__('MxChat Actions', 'mxchat'), esc_html__('Actions', 'mxchat'), 'manage_options', 'mxchat-actions', array($this, 'mxchat_actions_page_html') ); add_submenu_page( 'mxchat-max', esc_html__('Add Ons', 'mxchat'), esc_html__('Add Ons', 'mxchat'), 'manage_options', 'mxchat-addons', array($this, 'mxchat_create_addons_page') ); // Submenu page for Activation Key add_submenu_page( 'mxchat-max', esc_html__('Pro Upgrade', 'mxchat'), esc_html__('Pro Upgrade', 'mxchat'), 'manage_options', 'mxchat-activation', array($this, 'mxchat_create_activation_page') ); } public function mxchat_create_addons_page() { require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php'; $addons_page = new MxChat_Addons(); $addons_page->render_page(); } public function mxchat_save_setting_callback() { check_ajax_referer('mxchat_save_setting_nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]); } $name = isset($_POST['name']) ? $_POST['name'] : ''; // Strip slashes from the value before saving $value = isset($_POST['value']) ? stripslashes($_POST['value']) : ''; if (empty($name)) { wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]); } // Load the full options array $options = get_option('mxchat_options', []); // Handle special cases switch ($name) { case 'additional_popular_questions': $questions = json_decode($value, true); // No need for stripslashes here if (is_array($questions)) { $options[$name] = $questions; // Also update old option for backwards compatibility update_option('additional_popular_questions', $questions); } break; case 'email_blocker_header_content': // Allow HTML content but sanitize it safely $options[$name] = wp_kses_post($value); break; case 'similarity_threshold': // Save to the options array $options[$name] = $value; break; case 'user_message_bg_color': case 'user_message_font_color': case 'bot_message_bg_color': case 'bot_message_font_color': case 'top_bar_bg_color': case 'send_button_font_color': case 'chatbot_background_color': case 'icon_color': case 'chat_input_font_color': case 'live_agent_message_bg_color': case 'live_agent_message_font_color': case 'mode_indicator_bg_color': case 'mode_indicator_font_color': case 'toolbar_icon_color': // Store color values directly $options[$name] = $value; break; case 'live_agent_status': // Set the new value $options[$name] = ($value === 'on') ? 'on' : 'off'; break; case 'enable_woocommerce_integration': // Handle values that used to be 1/0 $options[$name] = ($value === 'on' || $value === '1') ? 'on' : 'off'; break; default: // Handle role rate limits if (strpos($name, 'mxchat_options[role_rate_limits]') !== false) { // Extract role ID from the name preg_match('/\[role_rate_limits\]\[(.*?)\]/', $name, $matches); if (isset($matches[1])) { $role_id = $matches[1]; // Initialize role_rate_limits if it doesn't exist if (!isset($options['role_rate_limits'])) { $options['role_rate_limits'] = []; } // Update the specific role's rate limit $options['role_rate_limits'][$role_id] = sanitize_text_field($value); break; } } // Handle toggles if (strpos($name, 'toggle') !== false || in_array($name, [ 'chat_persistence_toggle', 'privacy_toggle', 'complianz_toggle', 'chat_toolbar_toggle', 'show_pdf_upload_button', 'show_word_upload_button' ])) { $options[$name] = ($value === 'on') ? 'on' : 'off'; } else { // Store all other values directly $options[$name] = $value; } break; } // Save all updates to the options array $updated = update_option('mxchat_options', $options); // Always return success even if WordPress says nothing changed // (which happens when the value is the same as before) wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]); } /** * Helper function to compare if a value has changed * Handles various data types appropriately */ private function has_value_changed($old_value, $new_value) { // Handle null values if ($old_value === null && $new_value === '') { return false; } // Handle array values (like additional_popular_questions) if (is_array($old_value) && is_array($new_value)) { // Convert both to JSON for comparison to handle ordering differences return json_encode($old_value) !== json_encode($new_value); } // Handle toggle/checkbox values consistently if (in_array($old_value, ['on', '1', 1, true]) && in_array($new_value, ['on', '1', 1, true])) { return false; } if (in_array($old_value, ['off', '0', 0, false, '']) && in_array($new_value, ['off', '0', 0, false, ''])) { return false; } // Default direct comparison return $old_value !== $new_value; } /** * Handles AJAX auto-save for prompts and auto-sync settings. */ public function mxchat_save_prompts_setting_callback() { check_ajax_referer('mxchat_prompts_setting_nonce', '_ajax_nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => esc_html__('Unauthorized', 'mxchat')]); } $name = isset($_POST['name']) ? sanitize_text_field($_POST['name']) : ''; $value = isset($_POST['value']) ? sanitize_text_field($_POST['value']) : ''; if (empty($name)) { wp_send_json_error(['message' => esc_html__('Invalid field name', 'mxchat')]); } // Log the values we're trying to save (for debugging) //error_log('Attempting to save setting: ' . $name . ' = ' . $value); // For all auto-sync options if (strpos($name, 'mxchat_auto_sync_') === 0) { // Convert 'on'/'off' to '1'/'0' for consistency (if needed) $option_value = ($value === 'on') ? '1' : '0'; $result = update_option($name, $option_value); if ($result) { //error_log('Successfully saved setting: ' . $name . ' = ' . $option_value); wp_send_json_success(['message' => esc_html__('Setting saved', 'mxchat')]); } else { //error_log('Failed to save setting: ' . $name . ' (no changes or error)'); wp_send_json_error(['message' => esc_html__('Update failed or no changes', 'mxchat')]); } return; } // Handle fields stored in the 'mxchat_prompts_options' array. // Handle fields stored in the 'mxchat_prompts_options' array. if ( false !== strpos( $name, 'mxchat_prompts_options[' ) ) { // Extract the key name using regex. if ( preg_match( '/mxchat_prompts_options\[(.*?)\]/', $name, $matches ) && ! empty( $matches[1] ) ) { $key = sanitize_text_field( $matches[1] ); $options = get_option( 'mxchat_prompts_options', [] ); // Remove Pinecone-specific settings from the main plugin handling. if ( 'mxchat_use_pinecone' === $key ) { // Optionally, you could simply ignore this field or set a default value. // For example, ensure it's always 0 or remove it entirely: // $options[ $key ] = '0'; // Or, if you prefer, just return an error so that the main plugin doesn't handle it: wp_send_json_error( [ 'message' => esc_html__( 'Pinecone settings are now handled in the add-on.', 'mxchat' ) ] ); } else { // For all other fields, use the existing logic. $options[ $key ] = ( $value === 'on' || $value === '1' ) ? '1' : $value; } update_option( 'mxchat_prompts_options', $options ); wp_send_json_success( [ 'message' => esc_html__( 'Setting saved', 'mxchat' ) ] ); } } wp_send_json_error( [ 'message' => esc_html__( 'Field not recognized', 'mxchat' ) ] ); } public function mxchat_display_admin_notice() { // Success notice if ($message = get_transient('mxchat_admin_notice_success')) { ?>

MxChat Settings

is_activated): ?>

πŸš€ Limited Time Offer: Save $20 on MxChat Pro Lifetime Access!

Unlock unlimited access to our growing collection of powerful add-ons including Forms Builder, Theme Customizer, WooCommerce, Perplexity, and more – all included with your lifetime license!

prefix . 'mxchat_chat_transcripts'; // Get basic stats $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name"); $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name"); // Count unique users with detailed breakdown $total_users = $wpdb->get_var(" SELECT COUNT(DISTINCT CASE WHEN user_email != '' AND user_email IS NOT NULL THEN user_email WHEN user_id != 0 THEN CONCAT('user_', user_id) WHEN user_identifier NOT LIKE 'Tech-Savvy User' AND user_identifier NOT LIKE 'Detail-Oriented User' AND user_identifier NOT LIKE 'Language Learner' AND user_identifier NOT LIKE 'Casual Browser' AND user_identifier NOT LIKE 'Policy Enforcer' AND user_identifier NOT LIKE 'Researcher' AND user_identifier NOT LIKE 'Loyalty Member' AND user_identifier NOT LIKE 'Gift Buyer' AND user_identifier NOT LIKE 'Parent or Caregiver' THEN user_identifier ELSE session_id END ) FROM $table_name WHERE role != 'assistant' "); // Get user type breakdown $registered_users = $wpdb->get_var(" SELECT COUNT(DISTINCT user_email) FROM $table_name WHERE user_email != '' AND user_email IS NOT NULL "); $guest_users = $wpdb->get_var(" SELECT COUNT(DISTINCT user_identifier) FROM $table_name WHERE (user_email = '' OR user_email IS NULL) AND role != 'assistant' AND user_identifier NOT LIKE 'Tech-Savvy User' AND user_identifier NOT LIKE 'Detail-Oriented User' AND user_identifier NOT LIKE 'Language Learner' AND user_identifier NOT LIKE 'Casual Browser' AND user_identifier NOT LIKE 'Policy Enforcer' AND user_identifier NOT LIKE 'Researcher' AND user_identifier NOT LIKE 'Loyalty Member' AND user_identifier NOT LIKE 'Gift Buyer' AND user_identifier NOT LIKE 'Parent or Caregiver' "); // Get agent test messages count $agent_tests = $wpdb->get_var(" SELECT COUNT(DISTINCT session_id) FROM $table_name WHERE user_identifier IN ( 'Tech-Savvy User', 'Detail-Oriented User', 'Language Learner', 'Casual Browser', 'Policy Enforcer', 'Researcher', 'Loyalty Member', 'Gift Buyer', 'Parent or Caregiver' ) "); ?>

Chat Transcripts

πŸ’¬
πŸ“
πŸ‘₯
prefix . 'mxchat_chat_transcripts'; // Get all transcripts ordered by session and timestamp $results = $wpdb->get_results( "SELECT session_id, user_email, user_identifier, role, message, timestamp FROM {$table_name} ORDER BY session_id, timestamp ASC" ); if (empty($results)) { wp_send_json_error(array('message' => 'No transcripts found.')); wp_die(); } // Set headers for CSV download header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"'); header('Pragma: no-cache'); header('Expires: 0'); // Create output stream $output = fopen('php://output', 'w'); // Add UTF-8 BOM for proper Excel encoding fputs($output, "\xEF\xBB\xBF"); // Add CSV headers fputcsv($output, array( 'Session ID', 'Email', 'User Identifier', 'Role', 'Message', 'Timestamp' )); // Add data rows foreach ($results as $row) { fputcsv($output, array( $row->session_id, $row->user_email, $row->user_identifier, $row->role, $row->message, $row->timestamp )); } fclose($output); wp_die(); } public function mxchat_fetch_chat_history() { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have sufficient permissions to view this page.', 'mxchat')); } // First get unique session IDs ordered by most recent message in each session $session_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT session_id FROM {$table_name} GROUP BY session_id ORDER BY MAX(timestamp) DESC" ) ); if (empty($session_ids)) { wp_die(esc_html__('No chat history available.', 'mxchat')); } ob_start(); echo '
'; // Iterate through sessions from newest to oldest foreach ($session_ids as $session_id) { // Get the email associated with this session (if available) $email = $wpdb->get_var( $wpdb->prepare( "SELECT user_email FROM {$table_name} WHERE session_id = %s AND user_email != '' ORDER BY timestamp ASC LIMIT 1", $session_id ) ); // Debug: Check email retrieval if (empty($email)) { //error_log("No email found for session {$session_id}"); } else { //error_log("Email for session {$session_id}: " . $email); } // Get messages for this session ordered by timestamp $messages = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC", $session_id ) ); // Start session block echo '
'; echo '
'; // Wrap checkbox and session ID in one block echo '
'; echo ' '; echo '' . esc_html__('Session ID:', 'mxchat') . ' ' . esc_html($session_id); echo '
'; // Place email directly below the session ID block if (!empty($email)) { echo '
'; echo '' . esc_html__('Email:', 'mxchat') . ' ' . esc_html($email); echo '
'; } echo '
'; echo '
'; // Display messages for this session foreach ($messages as $transcript) { $formatted_timestamp = date_i18n('F j, Y g:i a', strtotime($transcript->timestamp)); // Determine message styling switch ($transcript->role) { case 'assistant': case 'bot': $message_class = 'bot-message'; $display_role = esc_html__('Chatbot', 'mxchat'); break; case 'user': $message_class = 'user-message'; $display_role = !empty($transcript->user_identifier) ? sanitize_text_field($transcript->user_identifier) : esc_html__('User', 'mxchat'); break; case 'agent': $message_class = 'agent-message'; $display_role = esc_html__('Agent', 'mxchat'); break; default: $message_class = 'unknown-message'; $display_role = esc_html__('Unknown', 'mxchat'); } // Process message content $message_content = wp_kses( stripslashes($transcript->message), [ 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [], 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [], 'a' => ['href' => [], 'title' => []] ] ); $message_content = nl2br($message_content); // Render message echo '
'; echo '
' . esc_html($display_role) . '
'; echo '
' . $message_content . '
'; echo '
' . esc_html($formatted_timestamp) . '
'; echo '
'; } // Close session block echo '
'; } echo '
'; $output = ob_get_clean(); echo $output; wp_die(); } public function mxchat_create_prompts_page() { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; // Display success message if all prompts were deleted if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') { echo '

' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '

'; } // Set up pagination and search query $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : ''; $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : ''; $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1; $per_page = 10; $offset = ($current_page - 1) * $per_page; // Modify query to handle search input $sql_search = ""; if ($search_query) { $sql_search = $wpdb->prepare("WHERE article_content LIKE %s", '%' . $wpdb->esc_like($search_query) . '%'); } // Retrieve total number of prompts, considering search filter $total_prompts = $wpdb->get_var("SELECT COUNT(*) FROM {$table_name} {$sql_search}"); $total_pages = ceil($total_prompts / $per_page); // Retrieve prompts from the database $prompts = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table_name} {$sql_search} ORDER BY timestamp DESC LIMIT %d OFFSET %d", $per_page, $offset ) ); // Add the pagination links generation here $page_links = paginate_links(array( 'base' => add_query_arg(array( 'paged' => '%#%', 'search' => urlencode($search_query), '_wpnonce' => wp_create_nonce('mxchat_prompts_search_nonce') ), admin_url('admin.php?page=mxchat-prompts')), 'format' => '', 'prev_text' => __('« Previous', 'mxchat'), 'next_text' => __('Next »', 'mxchat'), 'total' => $total_pages, 'current' => $current_page, )); // Retrieve processing statuses $pdf_url = get_transient('mxchat_last_pdf_url'); $sitemap_url = get_transient('mxchat_last_sitemap_url'); $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false; $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false; if ($pdf_status && $pdf_status['status'] === 'complete') { delete_transient('mxchat_last_pdf_url'); $pdf_status = false; } if ($sitemap_status && $sitemap_status['status'] === 'complete') { delete_transient('mxchat_last_sitemap_url'); $sitemap_status = false; } $is_processing = ($pdf_status && ($pdf_status['status'] === 'processing' || $pdf_status['status'] === 'error')) || ($sitemap_status && ($sitemap_status['status'] === 'processing' || $sitemap_status['status'] === 'error')); ?>

Knowledge Base Manager

options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002'; $has_openai_key = !empty($this->options['api_key']); $has_voyage_key = !empty($this->options['voyage_api_key']); // Determine if they have the needed API key for their selected embedding model $has_required_key = false; $required_key_type = ''; if (strpos($embedding_model, 'text-embedding-') !== false && $has_openai_key) { $has_required_key = true; $required_key_type = 'OpenAI'; } elseif (strpos($embedding_model, 'voyage-') !== false && $has_voyage_key) { $has_required_key = true; $required_key_type = 'Voyage AI'; } elseif (strpos($embedding_model, 'text-embedding-') !== false) { $required_key_type = 'OpenAI'; } elseif (strpos($embedding_model, 'voyage-') !== false) { $required_key_type = 'Voyage AI'; } ?>

You must have added credits to your %s account before using the knowledgebase.', 'mxchat'), $required_key_type, $required_key_type)); ?>

IMPORTANT:

get_single_url_status(); $is_active_processing = ($sitemap_status && $sitemap_status['status'] === 'processing') || ($pdf_status && $pdf_status['status'] === 'processing'); ?>

id); ?>
article_content))); ?>
source_url)) : ?>
prefix . 'mxchat_system_prompt_content'; // Delete all prompts from the table $wpdb->query("DELETE FROM {$table_name}"); // Clear relevant cache wp_cache_delete('all_prompts', 'mxchat_prompts'); // Redirect back with a success message $redirect_url = add_query_arg(array( 'page' => 'mxchat-prompts', 'all_deleted' => 'true' ), admin_url('admin.php')); wp_safe_redirect($redirect_url); exit; } public function mxchat_handle_delete_prompt() { // Sanitize and validate nonce $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : ''; if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) { wp_die(esc_html__('Nonce verification failed.', 'mxchat')); } // Check permissions if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat')); } // Validate and sanitize ID parameter $id = isset($_GET['id']) ? intval($_GET['id']) : 0; if ($id <= 0) { wp_die(esc_html__('Invalid prompt ID.', 'mxchat')); } global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; // Clear cache and delete prompt wp_cache_delete('prompt_' . $id, 'mxchat_prompts'); $wpdb->delete($table_name, array('id' => $id), array('%d')); wp_safe_redirect(add_query_arg(array('page' => 'mxchat-prompts', 'deleted' => 'true'), admin_url('admin.php'))); exit; } public function mxchat_generate_embedding($text) { // Enable detailed logging for debugging //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes'); //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...'); $options = get_option('mxchat_options'); $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002'; //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model); // Determine provider and endpoint if (strpos($selected_model, 'voyage') === 0) { $api_key = $options['voyage_api_key'] ?? ''; $endpoint = 'https://api.voyageai.com/v1/embeddings'; $provider_name = 'Voyage AI'; //error_log('[MXCHAT-EMBED] Using Voyage AI API'); } else { $api_key = $options['api_key'] ?? ''; $endpoint = 'https://api.openai.com/v1/embeddings'; $provider_name = 'OpenAI'; //error_log('[MXCHAT-EMBED] Using OpenAI API'); } //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint); if (empty($api_key)) { $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name); //error_log('[MXCHAT-EMBED] Error: ' . $error_message); return $error_message; } // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75) $estimated_tokens = ceil(str_word_count($text) / 0.75); //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens); if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false) { //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)'); // Consider truncating text here } // Prepare request body $request_body = array( 'model' => $selected_model, 'input' => $text ); // Add output_dimension for voyage-3-large model if ($selected_model === 'voyage-3-large') { $request_body['output_dimension'] = 2048; } //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model); // Make API request //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint); $response = wp_remote_post($endpoint, array( 'body' => wp_json_encode($request_body), 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ), 'timeout' => 60 // Increased timeout for large inputs )); // Handle wp_remote_post errors if (is_wp_error($response)) { $error_message = $response->get_error_message(); //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message); return 'Connection error: ' . $error_message; } // Get and check HTTP response code $http_code = wp_remote_retrieve_response_code($response); //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code); if ($http_code !== 200) { $error_body = wp_remote_retrieve_body($response); //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body); // Try to parse error for more details $error_json = json_decode($error_body, true); if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) { $error_type = $error_json['error']['type'] ?? 'unknown'; $error_message = $error_json['error']['message'] ?? 'No message'; //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type); //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message); // Customize error message for common API errors if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) { $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name); } elseif ($error_type === 'authentication_error') { $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name); } //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message); return $error_message; } $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code); //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message); return $error_message; } // Parse response body $response_body = wp_remote_retrieve_body($response); //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes'); $response_data = json_decode($response_body, true); if (json_last_error() !== JSON_ERROR_NONE) { $error = json_last_error_msg(); //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error); //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200)); return "Failed to parse API response: $error"; } // Both APIs use the same structure, so we can extract the embedding the same way if (isset($response_data['data'][0]['embedding'])) { $embedding_dimensions = count($response_data['data'][0]['embedding']); //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions'); // Check if embedding dimensions are as expected if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) || ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) { //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions'); } return $response_data['data'][0]['embedding']; } else { //error_log('[MXCHAT-EMBED] Error: No embedding found in response'); //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data))); if (isset($response_data['error'])) { $error_message = "API Error in response: " . wp_json_encode($response_data['error']); //error_log('[MXCHAT-EMBED] ' . $error_message); return $error_message; } $error_message = "Invalid API response format: No embedding found"; //error_log('[MXCHAT-EMBED] ' . $error_message); return $error_message; } } public function mxchat_delete_chat_history() { if (!current_user_can('manage_options')) { echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]); wp_die(); } check_ajax_referer('mxchat_delete_chat_history', 'security'); global $wpdb; $table_name = $wpdb->prefix . 'mxchat_chat_transcripts'; if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) { foreach ($_POST['delete_session_ids'] as $session_id) { $session_id_sanitized = sanitize_text_field($session_id); // Clear relevant cache before deletion $cache_key = 'chat_session_' . $session_id_sanitized; wp_cache_delete($cache_key, 'mxchat_chat_sessions'); // Perform the deletion $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]); } // Optionally, clear a general cache if you have one wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions'); echo wp_json_encode(['success' => esc_html__('Selected chat sessions have been deleted.', 'mxchat')]); } else { echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]); } wp_die(); } public function mxchat_save_inline_prompt() { // Check for nonce security check_ajax_referer('mxchat_save_inline_nonce'); // Verify permissions if (!current_user_can('manage_options')) { wp_send_json_error(esc_html__('Permission denied.', 'mxchat')); return; } global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; // Validate and sanitize input data $prompt_id = isset($_POST['id']) ? absint($_POST['id']) : 0; $article_content = isset($_POST['article_content']) ? sanitize_textarea_field($_POST['article_content']) : ''; $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : ''; if ($prompt_id > 0 && !empty($article_content)) { // Re-generate the embedding vector for the updated content $embedding_vector = $this->mxchat_generate_embedding($article_content); if (is_array($embedding_vector)) { // Serialize the embedding vector before storing it $embedding_vector_serialized = serialize($embedding_vector); // Update the prompt in the database $updated = $wpdb->update( $table_name, array( 'article_content' => $article_content, 'embedding_vector' => $embedding_vector_serialized, 'source_url' => $article_url, ), array('id' => $prompt_id), array('%s', '%s', '%s'), array('%d') ); if ($updated !== false) { wp_send_json_success(); } else { wp_send_json_error(esc_html__('Database update failed.', 'mxchat')); } } else { wp_send_json_error(esc_html__('Embedding generation failed.', 'mxchat')); } } else { wp_send_json_error(esc_html__('Invalid data.', 'mxchat')); } } /** * Get all public post types * * @return array Associative array of post type names and labels */ private function get_public_post_types() { $post_types = get_post_types(array('public' => true), 'objects'); $post_type_options = array(); foreach ($post_types as $post_type) { $post_type_options[$post_type->name] = $post_type->label; } return $post_type_options; } /** * Handle post updates and process content for the chatbot * * @param int $post_id The ID of the post being saved * @param WP_Post $post The post object * @param bool $update Whether this is an existing post being updated */ public function handle_post_update($post_id, $post, $update) { // Basic validation checks if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || wp_is_post_revision($post_id)) { return; } // Only process published content if ($post->post_status !== 'publish') { return; } $post_type = $post->post_type; // Check if sync is enabled for this post type $should_sync = false; // Check built-in post types first if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') { $should_sync = true; } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') { $should_sync = true; } else { // Check custom post types $option_name = 'mxchat_auto_sync_' . $post_type; if (get_option($option_name) === '1') { $should_sync = true; } } if (!$should_sync) { return; } // Prepare content and URL $content = wp_strip_all_tags($post->post_content); // For custom post types like job_listing, include additional fields if ($post_type === 'job_listing') { // Add title as it's important for job listings $content = $post->post_title . "\n\n" . $content; // Add job-specific meta if available $job_location = get_post_meta($post_id, '_job_location', true); if (!empty($job_location)) { $content .= "\n\nLocation: " . $job_location; } // Get job type terms $job_types = get_the_terms($post_id, 'job_listing_type'); if (!empty($job_types) && !is_wp_error($job_types)) { $types = array(); foreach ($job_types as $type) { $types[] = $type->name; } $content .= "\n\nJob Type: " . implode(', ', $types); } // Get company name if available $company_name = get_post_meta($post_id, '_company_name', true); if (!empty($company_name)) { $content .= "\n\nCompany: " . $company_name; } } $url = get_permalink($post_id); // Generate embedding vector $embedding_vector = $this->mxchat_generate_embedding($content); if (!$embedding_vector) { return; } // Check for Pinecone addon and its settings $pinecone_settings = get_option('mxchat_pinecone_addon_options'); $use_pinecone = false; if ($pinecone_settings && is_array($pinecone_settings)) { // Check if Pinecone is enabled and all required settings are present $use_pinecone = ( isset($pinecone_settings['mxchat_use_pinecone']) && $pinecone_settings['mxchat_use_pinecone'] === '1' && !empty($pinecone_settings['mxchat_pinecone_api_key']) && !empty($pinecone_settings['mxchat_pinecone_host']) && !empty($pinecone_settings['mxchat_pinecone_index']) && !empty($pinecone_settings['mxchat_pinecone_environment']) ); } if ($use_pinecone) { // Use Pinecone $pinecone_result = $this->store_in_pinecone_main( $embedding_vector, $content, $url, $pinecone_settings['mxchat_pinecone_api_key'], $pinecone_settings['mxchat_pinecone_environment'], $pinecone_settings['mxchat_pinecone_index'] ); if (!$pinecone_result['success']) { // Fallback to WordPress DB $this->store_in_wordpress_db($content, $url, $embedding_vector); } } else { // Fallback to WordPress DB storage $this->store_in_wordpress_db($content, $url, $embedding_vector); } } /** * Handle deletion of posts from both Pinecone and WordPress DB * * @param int $post_id The ID of the post being deleted * @return void */ public function mxchat_handle_post_delete($post_id) { // Get post data before it's deleted $post = get_post($post_id); // Basic validation if (!$post || wp_is_post_revision($post_id)) { return; } $post_type = $post->post_type; // Check if sync is enabled for this post type $should_sync = false; // Check built-in post types first if ($post_type === 'post' && get_option('mxchat_auto_sync_posts') === '1') { $should_sync = true; } else if ($post_type === 'page' && get_option('mxchat_auto_sync_pages') === '1') { $should_sync = true; } else { // Check custom post types $option_name = 'mxchat_auto_sync_' . $post_type; if (get_option($option_name) === '1') { $should_sync = true; } } if (!$should_sync) { return; } // Get the URL before post is deleted $source_url = get_permalink($post_id); if (!$source_url) { //error_log('MXChat: Failed to get permalink for post ' . $post_id); return; } // Check for Pinecone addon and its settings $pinecone_settings = get_option('mxchat_pinecone_addon_options'); $use_pinecone = false; if ($pinecone_settings && is_array($pinecone_settings)) { $use_pinecone = ( isset($pinecone_settings['mxchat_use_pinecone']) && $pinecone_settings['mxchat_use_pinecone'] === '1' && !empty($pinecone_settings['mxchat_pinecone_api_key']) && !empty($pinecone_settings['mxchat_pinecone_host']) && !empty($pinecone_settings['mxchat_pinecone_index']) && !empty($pinecone_settings['mxchat_pinecone_environment']) ); } $deletion_successful = false; if ($use_pinecone) { try { $pinecone_result = $this->delete_from_pinecone( array($source_url), $pinecone_settings['mxchat_pinecone_api_key'], $pinecone_settings['mxchat_pinecone_environment'], $pinecone_settings['mxchat_pinecone_index'] ); if (!$pinecone_result['success']) { //error_log('MXChat: Pinecone deletion failed for URL: ' . $source_url . ' - ' . $pinecone_result['message']); } else { $deletion_successful = true; } } catch (Exception $e) { //error_log('MXChat: Exception during Pinecone deletion - ' . $e->getMessage()); } } // Always attempt WordPress DB deletion, regardless of Pinecone status try { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; $result = $wpdb->delete( $table_name, array('source_url' => $source_url), array('%s') ); if ($result === false) { //error_log('MXChat: WordPress DB deletion failed for URL: ' . $source_url); } else { $deletion_successful = true; } } catch (Exception $e) { //error_log('MXChat: Exception during WordPress DB deletion - ' . $e->getMessage()); } if (!$deletion_successful) { //error_log('MXChat: Complete deletion failure for post ID: ' . $post_id . ' URL: ' . $source_url); } else { } } // Modified storage function to add type field private function store_in_pinecone_main($embedding_vector, $content, $url, $api_key, $environment, $index_name, $vector_id = null) { $vector_id = $vector_id ?: md5($url); $options = get_option('mxchat_pinecone_addon_options'); $host = $options['mxchat_pinecone_host'] ?? ''; if (empty($host)) { return array( 'success' => false, 'message' => 'Pinecone host is not configured. Please set the host in your settings.' ); } // Determine if this is a product URL $is_product = (strpos($url, '/product/') !== false || strpos($url, '/shop/') !== false); $api_endpoint = "https://{$host}/vectors/upsert"; $request_body = array( 'vectors' => array( array( 'id' => $vector_id, 'values' => $embedding_vector, 'metadata' => array( 'text' => $content, 'source_url' => $url, 'type' => $is_product ? 'product' : 'content', 'last_updated' => time() ) ) ) ); $response = wp_remote_post($api_endpoint, array( 'headers' => array( 'Api-Key' => $api_key, 'accept' => 'application/json', 'content-type' => 'application/json' ), 'body' => wp_json_encode($request_body), 'timeout' => 30, 'data_format' => 'body' )); if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { $body = wp_remote_retrieve_body($response); return array( 'success' => false, 'message' => sprintf( 'Pinecone API error (HTTP %d): %s', $response_code, $body ) ); } return array( 'success' => true, 'message' => 'Successfully stored in Pinecone' ); } // Helper method for WordPress DB storage private function store_in_wordpress_db($content, $url, $embedding_vector) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; $existing_entry = $wpdb->get_row( $wpdb->prepare("SELECT id FROM $table_name WHERE source_url = %s", $url) ); if ($existing_entry) { $wpdb->update( $table_name, array( 'article_content' => $content, 'embedding_vector' => serialize($embedding_vector), 'timestamp' => current_time('mysql') ), array('id' => $existing_entry->id), array('%s', '%s', '%s') ); } else { $wpdb->insert( $table_name, array( 'article_content' => $content, 'source_url' => $url, 'embedding_vector' => serialize($embedding_vector), 'timestamp' => current_time('mysql') ), array('%s', '%s', '%s', '%s') ); } } public function mxchat_handle_content_submission() { // Check if the form was submitted and the user has permission. if (!isset($_POST['submit_content']) || !current_user_can('manage_options')) { return; } // Verify the nonce. $nonce = isset($_POST['mxchat_submit_content_nonce']) ? sanitize_text_field(wp_unslash($_POST['mxchat_submit_content_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'mxchat_submit_content_action')) { wp_die(esc_html__('Nonce verification failed.', 'mxchat')); } // Sanitize the inputs. $article_content = sanitize_textarea_field($_POST['article_content']); $article_url = isset($_POST['article_url']) ? esc_url_raw($_POST['article_url']) : ''; // Generate the embedding vector. $embedding_vector = $this->mxchat_generate_embedding($article_content); if (!is_array($embedding_vector)) { set_transient('mxchat_admin_notice_error', esc_html__('Embedding generation failed. Please ensure your API key is correct and try again.', 'mxchat'), 30 ); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } // Store in the WordPress database. global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; // Ensure the source_url column exists. if ($wpdb->get_var($wpdb->prepare("SHOW COLUMNS FROM {$table_name} LIKE %s", 'source_url')) != 'source_url') { $wpdb->query($wpdb->prepare("ALTER TABLE {$table_name} ADD source_url VARCHAR(255) DEFAULT ''")); } // Serialize and store the embedding vector. $embedding_vector_serialized = serialize($embedding_vector); $inserted = $wpdb->insert( $table_name, array( 'article_content' => $article_content, 'embedding_vector' => $embedding_vector_serialized, 'source_url' => $article_url, ), array('%s', '%s', '%s') ); if ($inserted === false) { set_transient('mxchat_admin_notice_error', esc_html__('Error inserting content into the database. Please try again.', 'mxchat'), 30 ); } else { set_transient('mxchat_admin_notice_success', esc_html__('Content successfully submitted!', 'mxchat'), 30 ); } wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } private function is_pdf_url($url, $response) { $content_type = wp_remote_retrieve_header($response, 'content-type'); $file_extension = strtolower(pathinfo($url, PATHINFO_EXTENSION)); return strpos($content_type, 'pdf') !== false || $file_extension === 'pdf'; } private function handle_pdf_for_knowledge_base($pdf_url, $response) { if (!current_user_can('manage_options')) { //error_log(esc_html__('Unauthorized PDF processing attempt', 'mxchat')); return false; } $pdf_url = esc_url_raw($pdf_url); $upload_dir = wp_upload_dir(); if (isset($upload_dir['error']) && $upload_dir['error'] !== false) { //error_log(sprintf(esc_html__('Upload directory error: %s', 'mxchat'), esc_html($upload_dir['error']))); return false; } $pdf_filename = sanitize_file_name('mxchat_kb_' . time() . '.pdf'); $pdf_path = trailingslashit($upload_dir['path']) . $pdf_filename; $response_body = wp_remote_retrieve_body($response); if (empty($response_body)) { //error_log(esc_html__('Empty PDF response body', 'mxchat')); return false; } if (!wp_mkdir_p(dirname($pdf_path))) { //error_log(sprintf(esc_html__('Failed to create directory for PDF: %s', 'mxchat'), esc_html($pdf_path))); return false; } try { file_put_contents($pdf_path, $response_body); if (!file_exists($pdf_path)) { throw new Exception(__('Failed to save PDF file', 'mxchat')); } $parser = new \Smalot\PdfParser\Parser(); $pdf = $parser->parseFile($pdf_path); $total_pages = absint(count($pdf->getPages())); if ($total_pages < 1) { throw new Exception(__('Invalid PDF: no pages found', 'mxchat')); } wp_schedule_single_event(time(), 'mxchat_process_pdf_pages', array( 'pdf_path' => $pdf_path, 'pdf_url' => $pdf_url, 'total_pages' => $total_pages, 'batch_size' => absint(15), 'batch_pause' => absint(10) )); $status_data = array( 'total_pages' => $total_pages, 'processed_pages' => 0, 'status' => 'processing', 'last_update' => time() ); set_transient( sanitize_key('mxchat_pdf_status_' . md5($pdf_url)), array_map('sanitize_text_field', $status_data), DAY_IN_SECONDS ); return __('scheduled', 'mxchat'); } catch (Exception $e) { //error_log(sprintf(esc_html__('Error preparing PDF for processing: %s', 'mxchat'), esc_html($e->getMessage()))); if (file_exists($pdf_path)) { wp_delete_file($pdf_path); } return false; } } public static function process_pdf_pages_cron($pdf_path, $pdf_url, $total_pages, $batch_size, $batch_pause) { // Validate inputs $pdf_path = sanitize_text_field($pdf_path); $pdf_url = esc_url_raw($pdf_url); $total_pages = absint($total_pages); $batch_size = absint($batch_size); $batch_pause = absint($batch_pause); try { if (!file_exists($pdf_path)) { throw new Exception(sprintf('PDF file not found at path: %s', esc_html($pdf_path))); } $parser = new \Smalot\PdfParser\Parser(); $pdf = $parser->parseFile($pdf_path); $pages = $pdf->getPages(); // Get current progress $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url)); $status = get_transient($status_key); if (!$status || !is_array($status)) { throw new Exception('Invalid status data retrieved from transient'); } $start_page = absint($status['processed_pages']); $end_page = min($start_page + $batch_size, $total_pages); $instance = new self(); // Create an instance of the class $options = get_option('mxchat_options'); if (empty($options['api_key'])) { throw new Exception('API key is missing or invalid'); } for ($i = $start_page; $i < $end_page; $i++) { $text = $pages[$i]->getText(); if (empty($text)) { // Log empty page but continue processing //error_log(sprintf('[MXCHAT-PDF] Warning: Empty text on page %d of %s', $i + 1, $pdf_url)); continue; } $sanitized_content = $instance->mxchat_sanitize_content_for_api($text); // Call via instance if (empty($sanitized_content)) { // Log empty sanitized content but continue processing //error_log(sprintf('[MXCHAT-PDF] Warning: No valid content after sanitization on page %d of %s', $i + 1, $pdf_url)); continue; } $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Call via instance if (!is_array($embedding_vector)) { // If embedding generation fails, log error and throw exception $error_msg = is_string($embedding_vector) ? $embedding_vector : 'Unknown embedding generation error'; //error_log(sprintf('[MXCHAT-PDF] Error generating embedding for page %d: %s', $i + 1, $error_msg)); throw new Exception(sprintf('Failed to generate embedding for page %d: %s', $i + 1, $error_msg)); } $metadata = array( 'document_type' => 'pdf', 'total_pages' => $total_pages, 'current_page' => $i + 1, 'prev_page' => $i > 0 ? $i : null, 'next_page' => $i < ($total_pages - 1) ? $i + 2 : null, 'source_url' => $pdf_url ); $content_with_metadata = wp_json_encode($metadata) . "\n---\n" . $sanitized_content; $page_url = esc_url($pdf_url . "#page=" . ($i + 1)); $db_result = MxChat_Utils::submit_content_to_db($content_with_metadata, $page_url, $options['api_key']); if (is_wp_error($db_result)) { throw new Exception(sprintf('Failed to store content in database for page %d: %s', $i + 1, $db_result->get_error_message())); } // Update progress with sanitized data $status['processed_pages'] = absint($i + 1); $status['last_update'] = time(); set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS); } // Schedule next batch if needed if ($end_page < $total_pages) { wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_pdf_pages', array( 'pdf_path' => $pdf_path, 'pdf_url' => $pdf_url, 'total_pages' => $total_pages, 'batch_size' => $batch_size, 'batch_pause' => $batch_pause )); } else { // Processing complete $status['status'] = 'complete'; set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS); if (file_exists($pdf_path)) { wp_delete_file($pdf_path); } } } catch (\Exception $e) { //error_log(sprintf('[MXCHAT-PDF] Error processing PDF: %s', $e->getMessage())); // Get current status to update it $status_key = sanitize_key('mxchat_pdf_status_' . md5($pdf_url)); $status = get_transient($status_key); if (!$status || !is_array($status)) { $status = array( 'total_pages' => $total_pages, 'processed_pages' => 0, 'status' => 'error', 'error' => sanitize_text_field($e->getMessage()), 'last_update' => time() ); } else { $status['status'] = 'error'; $status['error'] = sanitize_text_field($e->getMessage()); $status['last_update'] = time(); } set_transient($status_key, array_map('sanitize_text_field', $status), DAY_IN_SECONDS); if (file_exists($pdf_path)) { wp_delete_file($pdf_path); } } } public function get_pdf_processing_status($pdf_url) { $pdf_url = esc_url_raw($pdf_url); $status = get_transient(sanitize_key('mxchat_pdf_status_' . md5($pdf_url))); if (!$status || !is_array($status)) { return false; } // Check for stalled processing (no updates for 5 minutes) if ($status['status'] === 'processing' && (time() - absint($status['last_update'])) > 300) { $status['status'] = 'error'; $status['error'] = __('PDF processing appears to be stalled. No updates for over 5 minutes.', 'mxchat'); // Save the updated status set_transient( sanitize_key('mxchat_pdf_status_' . md5($pdf_url)), array_map('sanitize_text_field', $status), DAY_IN_SECONDS ); } $result = array( 'total_pages' => absint($status['total_pages']), 'processed_pages' => absint($status['processed_pages']), 'percentage' => ($status['total_pages'] > 0) ? round((absint($status['processed_pages']) / absint($status['total_pages'])) * 100) : 0, 'status' => sanitize_text_field($status['status']), 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat') ); // Add error message if present if (isset($status['error']) && !empty($status['error'])) { $result['error'] = sanitize_text_field($status['error']); } return $result; } public function mxchat_handle_sitemap_submission() { // Start logging the submission process //error_log('[MXCHAT-URL] ===== Starting URL submission process ====='); // Check if the form was submitted and verify permissions if (!isset($_POST['submit_sitemap']) || !current_user_can('manage_options')) { //error_log('[MXCHAT-URL] Error: Unauthorized access or form not submitted properly'); wp_die(esc_html__('Unauthorized access', 'mxchat')); } // Verify nonce //error_log('[MXCHAT-URL] Verifying nonce'); check_admin_referer('mxchat_submit_sitemap_action', 'mxchat_submit_sitemap_nonce'); // Validate URL if (!isset($_POST['sitemap_url']) || empty($_POST['sitemap_url'])) { //error_log('[MXCHAT-URL] Error: Empty or missing URL'); set_transient('mxchat_admin_notice_error', esc_html__('Please provide a valid URL.', 'mxchat'), 30 ); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } $submitted_url = esc_url_raw($_POST['sitemap_url']); //error_log('[MXCHAT-URL] Processing URL: ' . $submitted_url); // Validate API key first $options = get_option('mxchat_options'); $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002'; if (strpos($selected_model, 'voyage') === 0) { $api_key = $options['voyage_api_key'] ?? ''; $provider_name = 'Voyage AI'; } else { $api_key = $options['api_key'] ?? ''; $provider_name = 'OpenAI'; } if (empty($api_key)) { $error_message = sprintf( esc_html__('%s API key is not configured. Please add your API key in the settings before submitting content.', 'mxchat'), $provider_name ); //error_log('[MXCHAT-URL] Error: ' . $error_message); set_transient('mxchat_admin_notice_error', $error_message, 30); //error_log('[MXCHAT-URL] Set error transient: ' . $error_message); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } //error_log('[MXCHAT-URL] Fetching URL content'); $response = wp_remote_get($submitted_url, array('timeout' => 30)); if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) { $error_message = is_wp_error($response) ? $response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($response); //error_log('[MXCHAT-URL] Error fetching URL: ' . $error_message); set_transient('mxchat_admin_notice_error', sprintf( esc_html__('Failed to fetch the URL: %s', 'mxchat'), esc_html($error_message) ), 30 ); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } $content_type = wp_remote_retrieve_header($response, 'content-type'); //error_log('[MXCHAT-URL] Content type: ' . $content_type); $body_content = wp_remote_retrieve_body($response); if (empty($body_content)) { //error_log('[MXCHAT-URL] Error: Empty response body'); set_transient('mxchat_admin_notice_error', esc_html__('Empty response received from URL.', 'mxchat'), 30 ); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } //error_log('[MXCHAT-URL] Retrieved body content length: ' . strlen($body_content) . ' bytes'); // Handle PDF URL if ($this->is_pdf_url($submitted_url, $response)) { //error_log('[MXCHAT-URL] Detected PDF URL, handling PDF for knowledge base'); $result = $this->handle_pdf_for_knowledge_base($submitted_url, $response); //error_log('[MXCHAT-URL] PDF handling result: ' . $result); if ($result === 'scheduled') { set_transient( 'mxchat_last_pdf_url', sanitize_text_field($submitted_url), DAY_IN_SECONDS ); set_transient('mxchat_admin_notice_info', esc_html__('PDF processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'), 30 ); } else { set_transient('mxchat_admin_notice_error', esc_html__('Failed to start PDF processing: ', 'mxchat') . esc_html($result), 30 ); } wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } // Handle Sitemap XML if (strpos($content_type, 'xml') !== false || strpos($body_content, 'message); } } set_transient('mxchat_admin_notice_error', esc_html__('Invalid sitemap XML. Please provide a valid sitemap.', 'mxchat'), 30 ); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } //error_log('[MXCHAT-URL] Valid XML found, handling sitemap for knowledge base'); $result = $this->handle_sitemap_for_knowledge_base($xml, $submitted_url); //error_log('[MXCHAT-URL] Sitemap handling result: ' . $result); if ($result === 'scheduled') { set_transient( 'mxchat_last_sitemap_url', sanitize_text_field($submitted_url), DAY_IN_SECONDS ); set_transient('mxchat_admin_notice_info', esc_html__('Sitemap processing has started in the background. You can check the progress in the Knowledge Base section.', 'mxchat'), 30 ); } else { // Return to the admin page without a redirect for better error display // The error is already stored in the sitemap status transient set_transient('mxchat_admin_notice_error', esc_html__('Failed to start sitemap processing. Please check the status below for details.', 'mxchat'), 30 ); } wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } // Handle Regular URL // Handle Regular URL //error_log('[MXCHAT-URL] Processing as regular webpage'); $page_content = $this->mxchat_extract_main_content($body_content); //error_log('[MXCHAT-URL] Extracted content length: ' . strlen($page_content) . ' bytes'); $sanitized_content = $this->mxchat_sanitize_content_for_api($page_content); //error_log('[MXCHAT-URL] Sanitized content length: ' . strlen($sanitized_content) . ' bytes'); if (empty($sanitized_content)) { //error_log('[MXCHAT-URL] Error: No valid content after sanitization'); // Set both transients - the error notice and the URL status set_transient('mxchat_admin_notice_error', esc_html__('No valid content found on the provided URL.', 'mxchat'), 30 ); // Set URL status transient set_transient('mxchat_single_url_status', [ 'url' => $submitted_url, 'timestamp' => current_time('mysql'), 'status' => 'failed', 'error' => esc_html__('No valid content found on the provided URL.', 'mxchat') ], DAY_IN_SECONDS); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } //error_log('[MXCHAT-URL] Generating embedding for content'); $embedding_vector = $this->mxchat_generate_embedding($sanitized_content); // Check if embedding_vector is a string (error message) if (is_string($embedding_vector)) { //error_log('[MXCHAT-URL] Error generating embedding: ' . $embedding_vector); $error_message = esc_html__('Failed to generate embedding: ', 'mxchat') . esc_html($embedding_vector); //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message); // Set both transients set_transient('mxchat_admin_notice_error', $error_message, 30); // Set URL status transient set_transient('mxchat_single_url_status', [ 'url' => $submitted_url, 'timestamp' => current_time('mysql'), 'status' => 'failed', 'error' => $error_message ], DAY_IN_SECONDS); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } if (is_array($embedding_vector)) { //error_log('[MXCHAT-URL] Successfully generated embedding with ' . count($embedding_vector) . ' dimensions'); $db_result = MxChat_Utils::submit_content_to_db( $sanitized_content, $submitted_url, $this->options['api_key'] ); if (is_wp_error($db_result)) { //error_log('[MXCHAT-URL] Error: Failed to store content in database: ' . $db_result->get_error_message()); $error_message = esc_html__('Failed to store content in database: ', 'mxchat') . esc_html($db_result->get_error_message()); //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message); // Set both transients set_transient('mxchat_admin_notice_error', $error_message, 30); // Set URL status transient set_transient('mxchat_single_url_status', [ 'url' => $submitted_url, 'timestamp' => current_time('mysql'), 'status' => 'failed', 'error' => $error_message ], DAY_IN_SECONDS); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } //error_log('[MXCHAT-URL] Successfully stored content in database'); $success_message = esc_html__('URL content successfully submitted!', 'mxchat'); //error_log('[MXCHAT-URL] Setting success transient: ' . $success_message); // Set both transients set_transient('mxchat_admin_notice_success', $success_message, 30); // Set URL status transient with success set_transient('mxchat_single_url_status', [ 'url' => $submitted_url, 'timestamp' => current_time('mysql'), 'status' => 'complete', 'content_length' => strlen($sanitized_content), 'embedding_dimensions' => count($embedding_vector) ], DAY_IN_SECONDS); } else { //error_log('[MXCHAT-URL] Error: Failed to generate embedding. Unexpected result type: ' . gettype($embedding_vector)); $error_message = esc_html__('Failed to generate embedding: Unexpected result type. Please check your API key and try again.', 'mxchat'); //error_log('[MXCHAT-URL] Setting error transient: ' . $error_message); // Set both transients set_transient('mxchat_admin_notice_error', $error_message, 30); // Set URL status transient set_transient('mxchat_single_url_status', [ 'url' => $submitted_url, 'timestamp' => current_time('mysql'), 'status' => 'failed', 'error' => $error_message ], DAY_IN_SECONDS); } //error_log('[MXCHAT-URL] ===== Completed URL submission process ====='); wp_safe_redirect(esc_url(admin_url('admin.php?page=mxchat-prompts'))); exit; } /** * Get the status of the last single URL submission */ private function get_single_url_status() { $status = get_transient('mxchat_single_url_status'); if (!$status) { return null; } // Add human-readable time if (isset($status['timestamp'])) { $status['human_time'] = human_time_diff(strtotime($status['timestamp']), current_time('timestamp')) . ' ' . __('ago', 'mxchat'); } return $status; } private function handle_sitemap_for_knowledge_base($xml, $sitemap_url) { // Clear any single URL status when starting sitemap processing delete_transient('mxchat_single_url_status'); if (!current_user_can('manage_options')) { //error_log(esc_html__('Unauthorized sitemap processing attempt', 'mxchat')); return false; } try { $sitemap_url = esc_url_raw($sitemap_url); if (!$xml || !is_object($xml)) { throw new Exception(__('Invalid XML object provided', 'mxchat')); } // Add embedding validation before processing // Test embedding with a small sample text to verify API key is working $test_result = $this->mxchat_generate_embedding("This is a test to verify the embedding API key is working."); // Check if test_result is a string (error message) rather than an array (valid embedding) if (is_string($test_result)) { //error_log('[MXCHAT-URL] Embedding API validation failed: ' . $test_result); // Store the error in the status transient so it can be displayed later $status_data = array( 'total_urls' => 0, 'processed_urls' => 0, 'status' => 'error', 'error' => __('Embedding API validation failed: ', 'mxchat') . $test_result, 'last_update' => time() ); set_transient( sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)), array_map('sanitize_text_field', $status_data), DAY_IN_SECONDS ); throw new Exception(__('Embedding API validation failed: ', 'mxchat') . $test_result); } // Make sure it's an array (valid embedding) if (!is_array($test_result)) { //error_log('[MXCHAT-URL] Embedding API returned unexpected result type: ' . gettype($test_result)); throw new Exception(__('Embedding API returned unexpected result type. Please check your configuration.', 'mxchat')); } $urls = []; foreach ($xml->url as $url_element) { $url = esc_url_raw((string)$url_element->loc); if ($url) { $urls[] = $url; } } $total_urls = absint(count($urls)); if ($total_urls < 1) { throw new Exception(__('No valid URLs found in sitemap', 'mxchat')); } wp_schedule_single_event(time(), 'mxchat_process_sitemap_urls', array( 'urls' => $urls, 'sitemap_url' => $sitemap_url, 'total_urls' => $total_urls, 'batch_size' => absint(10), 'batch_pause' => absint(5) )); $status_data = array( 'total_urls' => $total_urls, 'processed_urls' => 0, 'status' => 'processing', 'last_update' => time() ); set_transient( sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)), array_map('sanitize_text_field', $status_data), DAY_IN_SECONDS ); return __('scheduled', 'mxchat'); } catch (\Exception $e) { $error_message = $e->getMessage(); //error_log(sprintf(esc_html__('Error preparing sitemap for processing: %s', 'mxchat'), esc_html($error_message))); // Store the sitemap URL and error in transients so they can be displayed set_transient( 'mxchat_last_sitemap_url', sanitize_text_field($sitemap_url), DAY_IN_SECONDS ); $status_data = array( 'total_urls' => 0, 'processed_urls' => 0, 'status' => 'error', 'error' => $error_message, 'last_update' => time() ); set_transient( sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)), array_map('sanitize_text_field', $status_data), DAY_IN_SECONDS ); return $error_message; } } public static function process_sitemap_urls_cron($urls, $sitemap_url, $total_urls, $batch_size, $batch_pause) { // Validate inputs $sitemap_url = esc_url_raw($sitemap_url); $total_urls = absint($total_urls); $batch_size = absint($batch_size); $batch_pause = absint($batch_pause); if (!is_array($urls) || empty($urls)) { return; } try { $status_key = sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url)); $status = get_transient($status_key); if (!$status || !is_array($status)) { throw new Exception('Invalid status data retrieved from transient'); } // Initialize failed_urls array if it doesn't exist if (!isset($status['failed_urls_list']) || !is_array($status['failed_urls_list'])) { $status['failed_urls_list'] = []; } $start_url = absint($status['processed_urls']); $end_url = min($start_url + $batch_size, $total_urls); $instance = new self(); // Track failures in batch $batch_stats = [ 'processed' => 0, 'failed' => 0, 'last_error' => '', 'embedding_errors' => 0 // Track specifically embedding errors ]; // Check embedding configuration with first URL if ($start_url === 0) { $page_url = esc_url_raw($urls[0]); $page_response = wp_remote_get($page_url); if (!is_wp_error($page_response) && wp_remote_retrieve_response_code($page_response) === 200) { $page_html = wp_remote_retrieve_body($page_response); $page_content = $instance->mxchat_extract_main_content($page_html); $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content); if (!empty($sanitized_content)) { $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Check if embedding_vector is a string (error message) if (is_string($embedding_vector)) { throw new Exception('Embedding generation failed: ' . $embedding_vector); } // Make sure it's an array (valid embedding) if (!is_array($embedding_vector)) { throw new Exception('Embedding generation returned unexpected result type: ' . gettype($embedding_vector)); } } } } for ($i = $start_url; $i < $end_url; $i++) { $page_url = esc_url_raw($urls[$i]); $page_response = wp_remote_get($page_url); if (is_wp_error($page_response) || wp_remote_retrieve_response_code($page_response) !== 200) { $batch_stats['failed']++; $error_message = is_wp_error($page_response) ? $page_response->get_error_message() : 'HTTP Status: ' . wp_remote_retrieve_response_code($page_response); $batch_stats['last_error'] = 'Failed to fetch URL: ' . $error_message; // Add to failed URLs list with error message $status['failed_urls_list'][] = [ 'url' => $page_url, 'error' => $error_message, 'time' => time() ]; continue; } $page_html = wp_remote_retrieve_body($page_response); $page_content = $instance->mxchat_extract_main_content($page_html); $sanitized_content = $instance->mxchat_sanitize_content_for_api($page_content); if (!empty($sanitized_content)) { $embedding_vector = $instance->mxchat_generate_embedding($sanitized_content); // Check if embedding_vector is a string (error message) if (is_string($embedding_vector)) { $batch_stats['failed']++; $batch_stats['embedding_errors']++; $batch_stats['last_error'] = 'Failed to generate embedding: ' . $embedding_vector; // Add to failed URLs list with error message $status['failed_urls_list'][] = [ 'url' => $page_url, 'error' => 'Embedding error: ' . $embedding_vector, 'time' => time() ]; // If we have multiple embedding errors, stop processing if ($batch_stats['embedding_errors'] >= 10) { throw new Exception('Multiple embedding failures detected: ' . $embedding_vector); } continue; } // Check if it's an array (valid embedding) if (is_array($embedding_vector)) { $options = get_option('mxchat_options'); $submission_result = MxChat_Utils::submit_content_to_db($sanitized_content, $page_url, $options['api_key']); if (is_wp_error($submission_result)) { $batch_stats['failed']++; $batch_stats['last_error'] = $submission_result->get_error_message(); // Add to failed URLs list with error message $status['failed_urls_list'][] = [ 'url' => $page_url, 'error' => 'Database submission error: ' . $submission_result->get_error_message(), 'time' => time() ]; continue; } $batch_stats['processed']++; } else { $batch_stats['failed']++; $batch_stats['embedding_errors']++; $batch_stats['last_error'] = 'Failed to generate embedding: Unexpected result type: ' . gettype($embedding_vector); // Add to failed URLs list with error message $status['failed_urls_list'][] = [ 'url' => $page_url, 'error' => 'Embedding error: Unexpected result type: ' . gettype($embedding_vector), 'time' => time() ]; // If we have multiple embedding errors, stop processing if ($batch_stats['embedding_errors'] >= 10) { throw new Exception('Multiple embedding failures detected. Please check your embedding API configuration.'); } } } $status['processed_urls'] = absint($i + 1); $status['last_update'] = time(); $status['failed_urls'] = absint($status['failed_urls'] ?? 0) + $batch_stats['failed']; $status['last_error'] = $batch_stats['last_error']; // Limit the number of failed URLs we store to prevent transient size issues if (count($status['failed_urls_list']) > 100) { $status['failed_urls_list'] = array_slice($status['failed_urls_list'], -100); } set_transient($status_key, $status, DAY_IN_SECONDS); } // If all URLs in this batch failed, stop processing if ($batch_stats['processed'] === 0 && $batch_stats['failed'] > 0) { $status['status'] = 'error'; $status['error'] = sprintf( 'Processing stopped: %d consecutive failures. Last error: %s', $batch_stats['failed'], $batch_stats['last_error'] ); set_transient($status_key, $status, DAY_IN_SECONDS); return; } if ($end_url < $total_urls) { wp_schedule_single_event(time() + $batch_pause, 'mxchat_process_sitemap_urls', array( 'urls' => $urls, 'sitemap_url' => $sitemap_url, 'total_urls' => $total_urls, 'batch_size' => $batch_size, 'batch_pause' => $batch_pause, )); } else { $status['status'] = 'complete'; set_transient($status_key, $status, DAY_IN_SECONDS); } } catch (\Exception $e) { $status['status'] = 'error'; $status['error'] = $e->getMessage(); set_transient($status_key, $status, DAY_IN_SECONDS); } } private function mxchat_sanitize_content_for_api($content) { //error_log('[MXCHAT-SANITIZE] Original content preview: ' . substr($content, 0, 500) . '...'); // Remove script, style tags, and HTML comments $content = preg_replace('/]*>(.*?)<\/script>/is', '', $content); $content = preg_replace('/]*>(.*?)<\/style>/is', '', $content); $content = preg_replace('//', '', $content); // Remove all HTML tags and decode HTML entities $content = wp_strip_all_tags($content); $content = html_entity_decode($content, ENT_QUOTES | ENT_HTML5); // Trim and normalize whitespace $content = trim(preg_replace('/\s+/', ' ', $content)); // Remove control characters (which can cause database issues) $content = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $content); // Remove NULL bytes which can cause database errors $content = str_replace("\0", "", $content); // Ensure valid UTF-8 encoding $content = wp_check_invalid_utf8($content); // Remove any extremely long strings without spaces (often garbage) $content = preg_replace('/\S{300,}/', ' ', $content); // Replace problematic characters that often cause database issues $content = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $content); // Remove emoji and other high Unicode characters // Replace any remaining potentially problematic characters with spaces $content = preg_replace('/[^\p{L}\p{N}\p{P}\p{Z}\p{Sm}]/u', ' ', $content); // Limit to reasonable length if needed $max_length = 65000; // Just under MySQL TEXT field limit if (strlen($content) > $max_length) { $content = substr($content, 0, $max_length); } //error_log('[MXCHAT-SANITIZE] Sanitized content preview: ' . substr($content, 0, 500) . '...'); return $content; } private static function mxchat_extract_main_content($html) { if (empty($html)) { return ''; } try { $dom = new DOMDocument; libxml_use_internal_errors(true); // Suppress HTML parsing errors @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $xpath = new DOMXPath($dom); // For debugging purposes $debugEnabled = false; // Set to true to enable debugging output $debug = function($message) use ($debugEnabled) { if ($debugEnabled) { //error_log('[MXCHAT-DEBUG] ' . $message); } }; // Direct targeting for Gerow theme posts $post_text = $xpath->query('//div[contains(@class, "post-text")]'); if ($post_text && $post_text->length > 0) { $debug("Found post-text directly"); $content = ''; foreach ($post_text as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning post-text content"); return $content; } } // Try to get the blog details content which contains the post-text $blog_details = $xpath->query('//div[contains(@class, "blog-details-content")]'); if ($blog_details && $blog_details->length > 0) { $debug("Found blog-details-content"); $content = ''; foreach ($blog_details as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning blog-details-content"); return $content; } } // Try to get the article which contains the blog details $article = $xpath->query('//article[contains(@class, "blog-details-wrap")]'); if ($article && $article->length > 0) { $debug("Found article with blog-details-wrap"); $content = ''; foreach ($article as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning article content"); return $content; } } // Try even broader with the blog-item-wrap $blog_item = $xpath->query('//div[contains(@class, "blog-item-wrap")]'); if ($blog_item && $blog_item->length > 0) { $debug("Found blog-item-wrap"); $content = ''; foreach ($blog_item as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning blog-item-wrap content"); return $content; } } // Specific Gerow theme path $gerow_path = $xpath->query('//section[contains(@class, "blog-area")]//div[contains(@class, "post-text")]'); if ($gerow_path && $gerow_path->length > 0) { $debug("Found Gerow theme path to post-text"); $content = ''; foreach ($gerow_path as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning Gerow post-text content"); return $content; } } // Generic blog post selectors $selectors = [ // Blog post specific selectors '//div[contains(@class, "post-text")]', '//article[contains(@class, "blog-post-item")]//div[contains(@class, "post-text")]', '//div[contains(@class, "blog-details-content")]', '//article[contains(@class, "blog-details-wrap")]', '//div[contains(@class, "entry-content")]', '//div[contains(@class, "blog-content")]', '//div[contains(@class, "blog-item-wrap")]', // More general content selectors '//div[contains(@class, "page__content")]', '//div[contains(@class, "elementor-widget-container")]', '//div[contains(@class, "elementor-text-editor")]', '//div[contains(@class, "elementor-widget-text-editor")]', '//*[contains(@class, "entry-content")]', '//*[contains(@class, "post-content")]', '//*[contains(@class, "article-content")]', '//*[@id="content"]', '//*[@id="main-content"]', '//section[contains(@class, "blog-area")]', '//article', '//main', '//div[contains(@class, "content")]' ]; // First handle Elementor content $debug("Checking for Elementor content"); $elementor_widgets = $xpath->query('//div[contains(@class, "elementor-element")]//div[contains(@class, "elementor-widget-container")]'); if ($elementor_widgets && $elementor_widgets->length > 0) { $debug("Found Elementor widgets"); $combined_content = ''; foreach ($elementor_widgets as $widget) { $widget_content = $dom->saveHTML($widget); if (!empty($widget_content)) { $combined_content .= $widget_content; } } if (!empty($combined_content)) { $debug("Returning Elementor content"); return $combined_content; } } // Try standard selectors one by one foreach ($selectors as $selector) { $debug("Trying selector: " . $selector); $nodes = $xpath->query($selector); if ($nodes && $nodes->length > 0) { $debug("Found matches for selector: " . $selector); $content = ''; foreach ($nodes as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning content from selector: " . $selector); return $content; } } } // Manual regex fallback for post-text if DOM methods fail $debug("Trying regex fallback"); if (preg_match('/
(.*?)<\/div>\s*<\/div>\s*<\/div>/s', $html, $matches)) { $debug("Found post-text via regex"); return '
' . $matches[1] . '
'; } // Try to extract the blog section as a whole $blog_section = $xpath->query('//section[contains(@class, "blog-area")]'); if ($blog_section && $blog_section->length > 0) { $debug("Found blog-area section"); $content = ''; foreach ($blog_section as $node) { $content .= $dom->saveHTML($node); } if (!empty($content)) { $debug("Returning blog-area section content"); return $content; } } // Fallback: Return the body content if no specific selector matches $debug("Using body fallback"); $body = $dom->getElementsByTagName('body'); if ($body->length > 0) { return $dom->saveHTML($body->item(0)); } // Last resort: return the original HTML $debug("Returning original HTML"); return $html; } catch (Exception $e) { //error_log('[MXCHAT-ERROR] Content extraction failed: ' . $e->getMessage()); return $html; // Return original HTML if parsing fails } finally { libxml_clear_errors(); } } public function get_sitemap_processing_status($sitemap_url) { $sitemap_url = esc_url_raw($sitemap_url); $status = get_transient(sanitize_key('mxchat_sitemap_status_' . md5($sitemap_url))); if (!$status || !is_array($status)) { return false; } return array( 'total_urls' => absint($status['total_urls']), 'processed_urls' => absint($status['processed_urls']), 'failed_urls' => absint($status['failed_urls'] ?? 0), 'percentage' => ($status['total_urls'] > 0) ? round((absint($status['processed_urls']) / absint($status['total_urls'])) * 100) : 0, 'status' => sanitize_text_field($status['status']), 'last_update' => human_time_diff(absint($status['last_update']), time()) . ' ' . esc_html__('ago', 'mxchat'), 'error' => isset($status['error']) ? sanitize_text_field($status['error']) : '', 'last_error' => isset($status['last_error']) ? sanitize_text_field($status['last_error']) : '', 'failed_urls_list' => isset($status['failed_urls_list']) ? $status['failed_urls_list'] : array() ); } public function ajax_get_status_updates() { // Verify the request check_ajax_referer('mxchat_status_nonce', 'nonce'); // Get the status just like in your admin page $pdf_url = get_transient('mxchat_last_pdf_url'); $sitemap_url = get_transient('mxchat_last_sitemap_url'); $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false; $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false; // Check for true processing status, not just presence of status $is_active_processing = ($sitemap_status && $sitemap_status['status'] === 'processing') || ($pdf_status && $pdf_status['status'] === 'processing'); // Get single URL status, but only if no sitemap/PDF is processing $single_url_status = !$is_active_processing ? $this->get_single_url_status() : false; // Only clear transients for completed processes, not error states if ($pdf_status && $pdf_status['status'] === 'complete') { delete_transient('mxchat_last_pdf_url'); $pdf_status = false; } if ($sitemap_status && $sitemap_status['status'] === 'complete') { delete_transient('mxchat_last_sitemap_url'); $sitemap_status = false; } // Return JSON response with the status data wp_send_json(array( 'pdf_status' => $pdf_status, 'sitemap_status' => $sitemap_status, 'single_url_status' => $single_url_status, 'is_processing' => $is_active_processing )); } public function mxchat_stop_processing() { // Verify permissions if (!current_user_can('manage_options')) { wp_die(esc_html__('Unauthorized access', 'mxchat')); } // Verify nonce check_admin_referer('mxchat_stop_processing_action', 'mxchat_stop_processing_nonce'); // Get the last sitemap URL and clear its transient $sitemap_url = get_transient('mxchat_last_sitemap_url'); if ($sitemap_url) { delete_transient('mxchat_sitemap_status_' . md5($sitemap_url)); delete_transient('mxchat_last_sitemap_url'); } // Get the last PDF URL and clear its transient $pdf_url = get_transient('mxchat_last_pdf_url'); if ($pdf_url) { delete_transient('mxchat_pdf_status_' . md5($pdf_url)); delete_transient('mxchat_last_pdf_url'); } // Unschedule any pending sitemap events $timestamp = wp_next_scheduled('mxchat_process_sitemap_urls'); if ($timestamp) { wp_unschedule_event($timestamp, 'mxchat_process_sitemap_urls'); } // Redirect back with a success message set_transient('mxchat_admin_notice_success', esc_html__('Processing has been stopped successfully.', 'mxchat'), 30 ); wp_safe_redirect(admin_url('admin.php?page=mxchat-prompts')); exit; } public function mxchat_handle_product_change($post_id, $post, $update) { if ($post->post_type !== 'product') { return; } if ($post->post_status === 'publish') { add_action('shutdown', function() use ($post_id) { $product = wc_get_product($post_id); if ($product) { $this->mxchat_store_product_embedding($product); } }); } } private function mxchat_store_product_embedding($product) { if (!isset($this->options['enable_woocommerce_integration']) || !in_array($this->options['enable_woocommerce_integration'], ['1', 'on'])) { return; } $source_url = get_permalink($product->get_id()); // Build description $regular_price = $product->get_regular_price(); $sale_price = $product->get_sale_price(); $description = $product->get_description() . "\n\n" . esc_html__('Short Description:', 'mxchat') . " " . $product->get_short_description() . "\n" . esc_html__('Price:', 'mxchat') . " " . $regular_price . "\n" . esc_html__('Sale Price:', 'mxchat') . " " . ($sale_price ?: esc_html__('N/A', 'mxchat')) . "\n" . esc_html__('SKU:', 'mxchat') . " " . $product->get_sku(); // Generate embedding vector $embedding_vector = $this->mxchat_generate_embedding($description); if (!is_array($embedding_vector)) { return; } // Check for Pinecone addon and its settings $pinecone_settings = get_option('mxchat_pinecone_addon_options'); $use_pinecone = false; if ($pinecone_settings && is_array($pinecone_settings)) { $use_pinecone = ( isset($pinecone_settings['mxchat_use_pinecone']) && $pinecone_settings['mxchat_use_pinecone'] === '1' && !empty($pinecone_settings['mxchat_pinecone_api_key']) && !empty($pinecone_settings['mxchat_pinecone_host']) && !empty($pinecone_settings['mxchat_pinecone_index']) && !empty($pinecone_settings['mxchat_pinecone_environment']) ); } if ($use_pinecone) { $result = $this->store_in_pinecone_main( $embedding_vector, $description, $source_url, $pinecone_settings['mxchat_pinecone_api_key'], $pinecone_settings['mxchat_pinecone_environment'], $pinecone_settings['mxchat_pinecone_index'] ); if (!$result['success']) { // Fallback to WordPress DB if Pinecone storage fails $this->store_in_wordpress_db($description, $source_url, $embedding_vector); } } else { // Use WordPress DB storage $this->store_in_wordpress_db($description, $source_url, $embedding_vector); } } public function mxchat_handle_product_delete($post_id) { if (get_post_type($post_id) !== 'product') { return; } $source_url = get_permalink($post_id); // Check for Pinecone addon and its settings $pinecone_settings = get_option('mxchat_pinecone_addon_options'); $use_pinecone = false; if ($pinecone_settings && is_array($pinecone_settings)) { $use_pinecone = ( isset($pinecone_settings['mxchat_use_pinecone']) && $pinecone_settings['mxchat_use_pinecone'] === '1' && !empty($pinecone_settings['mxchat_pinecone_api_key']) && !empty($pinecone_settings['mxchat_pinecone_host']) && !empty($pinecone_settings['mxchat_pinecone_index']) && !empty($pinecone_settings['mxchat_pinecone_environment']) ); } if ($use_pinecone) { $this->delete_from_pinecone( array($source_url), $pinecone_settings['mxchat_pinecone_api_key'], $pinecone_settings['mxchat_pinecone_environment'], $pinecone_settings['mxchat_pinecone_index'] ); } else { // Fallback to WordPress DB deletion global $wpdb; $table_name = $wpdb->prefix . 'mxchat_system_prompt_content'; $wpdb->delete($table_name, array('source_url' => $source_url), array('%s')); } } /** * Delete vectors from Pinecone by source URL * * @param array $urls Array of source URLs to delete * @param string $api_key Pinecone API key * @param string $environment Pinecone environment * @param string $index_name Pinecone index name * @return array Associative array with 'success' boolean and 'message' string */ private function delete_from_pinecone($urls, $api_key, $environment, $index_name) { // Get the Pinecone host from options (matching your store_in_pinecone_main pattern) $options = get_option('mxchat_pinecone_addon_options'); $host = $options['mxchat_pinecone_host'] ?? ''; if (empty($host)) { return array( 'success' => false, 'message' => 'Pinecone host is not configured. Please set the host in your settings.' ); } // Build API endpoint using the configured host $api_endpoint = "https://{$host}/vectors/delete"; // Create vector IDs from URLs (matching your store method's ID generation) $vector_ids = array_map('md5', $urls); // Prepare the delete request body $request_body = array( 'ids' => $vector_ids, 'filter' => array( 'source_url' => array( '$in' => $urls ) ) ); // Make the deletion request $response = wp_remote_post($api_endpoint, array( 'headers' => array( 'Api-Key' => $api_key, 'accept' => 'application/json', 'content-type' => 'application/json' ), 'body' => wp_json_encode($request_body), 'timeout' => 30, 'data_format' => 'body' )); // Handle WordPress HTTP API errors if (is_wp_error($response)) { return array( 'success' => false, 'message' => $response->get_error_message() ); } // Check response status $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { $body = wp_remote_retrieve_body($response); return array( 'success' => false, 'message' => sprintf( 'Pinecone API error (HTTP %d): %s', $response_code, $body ) ); } // Parse response body $body = wp_remote_retrieve_body($response); $response_data = json_decode($body, true); // Final validation of the response if (json_last_error() !== JSON_ERROR_NONE) { return array( 'success' => false, 'message' => 'Failed to parse Pinecone response: ' . json_last_error_msg() ); } return array( 'success' => true, 'message' => sprintf('Successfully deleted %d vectors from Pinecone', count($vector_ids)) ); } public function mxchat_create_activation_page() { $license_status = get_option('mxchat_license_status', 'inactive'); $license_error = get_option('mxchat_license_error', ''); ?>

Activate MxChat Pro

prefix . 'mxchat_intents'; $page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1; $per_page = 20; $offset = ($page - 1) * $per_page; // Success message if (isset($_GET['updated']) && $_GET['updated'] === 'true') { echo '

' . esc_html__('Action updated successfully.', 'mxchat') . '

'; } // Filtering logic $where = '1=1'; $search_term = isset($_GET['s']) ? trim($_GET['s']) : ''; $callback_filter = isset($_GET['callback_filter']) ? sanitize_text_field($_GET['callback_filter']) : ''; if ($search_term) { $search_term_like = '%' . $wpdb->esc_like($search_term) . '%'; $where .= $wpdb->prepare(' AND (intent_label LIKE %s OR phrases LIKE %s)', $search_term_like, $search_term_like); } if ($callback_filter) { $where .= $wpdb->prepare(' AND callback_function = %s', $callback_filter); } // Pagination $total_intents = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE $where"); $total_pages = ceil($total_intents / $per_page); // Get intents (now called actions) $actions = $wpdb->get_results($wpdb->prepare( "SELECT * FROM $table_name WHERE $where LIMIT %d OFFSET %d", $per_page, $offset )); // Get callbacks $available_callbacks = $this->mxchat_get_available_callbacks(); ?>

Actions Manager

callback_function; $callback_label = isset($available_callbacks[$callback_function]['label']) ? $available_callbacks[$callback_function]['label'] : $callback_function; $threshold_value = isset($action->similarity_threshold) ? round($action->similarity_threshold * 100) : 85; // Check if this is a form action $is_form_action = strpos($action->intent_label, 'Form ') === 0; // Get action status (enabled/disabled) - default to true if column doesn't exist $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true; ?>
intent_label); ?>
get_trimmed_phrases($action->phrases)); } else { echo esc_html(strlen($action->phrases) > 100 ? substr($action->phrases, 0, 97) . '...' : $action->phrases); } ?>
%

1) : ?>
add_query_arg('paged', '%#%'), 'format' => '', 'prev_text' => __('« Previous', 'mxchat'), 'next_text' => __('Next »', 'mxchat'), 'total' => $total_pages, 'current' => $page )); ?>
'Security check failed')); return; } // Check permissions if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => 'Permission denied')); return; } // Validate params $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0; $enabled = isset($_POST['enabled']) ? (bool)$_POST['enabled'] : false; if (!$intent_id) { wp_send_json_error(array('message' => 'Invalid action ID')); return; } // Update the intent/action status in the database global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; // Using the 'enabled' field - add this field if it doesn't exist $result = $wpdb->update( $table_name, array('enabled' => $enabled ? 1 : 0), array('id' => $intent_id), array('%d'), array('%d') ); if ($result === false) { wp_send_json_error(array('message' => 'Database error')); return; } wp_send_json_success(); } /** * Add the 'enabled' column to the intents table if it doesn't exist * Call this during plugin activation or update */ public function mxchat_add_enabled_column_to_intents() { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; // Check if the column already exists $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'"); if (empty($columns)) { // Add the column with default value of 1 (enabled) $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1"); } } /** * Handle embedding generation errors using existing admin notice system * * @param string $message Error message to display * @param bool $redirect Whether to redirect back to the actions page * @return void */ private function handle_embedding_error($message, $redirect = true) { // Store the error message in the existing transient set_transient('mxchat_admin_notice_error', $message, 60); if ($redirect) { // Redirect back to the actions page $redirect_url = add_query_arg( array( 'page' => 'mxchat-actions' ), admin_url('admin.php') ); wp_safe_redirect($redirect_url); exit; } } /** * Handle editing of intent phrases - with improved error handling * * @since 1.0.0 * @return void */ public function mxchat_handle_edit_intent() { // Security checks (nonce and permissions) if (!current_user_can('manage_options')) { wp_die(esc_html__('Unauthorized user', 'mxchat')); } check_admin_referer('mxchat_edit_intent'); // Get POST data $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0; $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : ''; $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : ''; $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85; $similarity_threshold = min(95, max(70, $threshold_percentage)) / 100; // Convert to 0.70–0.95 // Validate inputs if (!$intent_id || empty($intent_label) || empty($phrases_input)) { $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat')); return; } $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input)))); if (empty($phrases_array)) { $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat')); return; } // Generate embeddings with improved error handling $vectors = []; $failed_phrases = []; foreach ($phrases_array as $phrase) { $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']); if (is_array($embedding_vector)) { $vectors[] = $embedding_vector; } else { $failed_phrases[] = $phrase; } } if (!empty($failed_phrases)) { $this->handle_embedding_error( sprintf( __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'), implode(', ', $failed_phrases) ) ); return; } if (empty($vectors)) { $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat')); return; } $combined_vector = $this->mxchat_average_vectors($vectors); $serialized_vector = maybe_serialize($combined_vector); // Update the database global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; $result = $wpdb->update( $table_name, array( 'intent_label' => $intent_label, 'phrases' => implode(', ', $phrases_array), 'embedding_vector' => $serialized_vector, 'similarity_threshold' => $similarity_threshold ), array('id' => $intent_id), array('%s', '%s', '%s', '%f'), // Format: string, string, string, float array('%d') // Where format: integer ); if (false === $result) { $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat')); return; } // Set success message and redirect set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60); $redirect_url = add_query_arg( array( 'page' => 'mxchat-actions' ), admin_url('admin.php') ); wp_safe_redirect($redirect_url); exit; } /** * Handle adding new intent - with improved error handling * * @return void */ public function mxchat_handle_add_intent() { if (!current_user_can('manage_options')) { wp_die(esc_html__('Unauthorized user', 'mxchat')); } check_admin_referer('mxchat_add_intent_nonce'); global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : ''; $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : ''; $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : ''; $default_threshold = 0.85; if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) { $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat')); return; } $available_callbacks = $this->mxchat_get_available_callbacks(); if (!array_key_exists($callback_function, $available_callbacks)) { $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat')); return; } $is_pro_only = $available_callbacks[$callback_function]['pro_only']; if ($is_pro_only && !$this->is_activated) { $this->handle_embedding_error(__('This callback function is available in the Pro version only.', 'mxchat')); return; } $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input)))); if (empty($phrases_array)) { $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat')); return; } // Generate embeddings with improved error handling $vectors = []; $failed_phrases = []; foreach ($phrases_array as $phrase) { $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']); if (is_array($embedding_vector)) { $vectors[] = $embedding_vector; } else { $failed_phrases[] = $phrase; } } if (!empty($failed_phrases)) { $this->handle_embedding_error( sprintf( __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'), implode(', ', $failed_phrases) ) ); return; } if (empty($vectors)) { $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat')); return; } $combined_vector = $this->mxchat_average_vectors($vectors); $serialized_vector = maybe_serialize($combined_vector); $result = $wpdb->insert($table_name, [ 'intent_label' => $intent_label, 'phrases' => implode(', ', $phrases_array), 'embedding_vector' => $serialized_vector, 'callback_function' => $callback_function, 'similarity_threshold' => $default_threshold, ]); if ($result === false) { $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error); return; } // Set success message set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60); wp_safe_redirect(admin_url('admin.php?page=mxchat-actions')); exit; } /** * Update intent threshold with AJAX support */ public function mxchat_update_intent_threshold() { // Check permissions if (!current_user_can('manage_options')) { if (wp_doing_ajax()) { wp_send_json_error(array('message' => 'Unauthorized user')); return; } wp_die(esc_html__('Unauthorized user', 'mxchat')); } // Verify nonce check_admin_referer('mxchat_update_intent_threshold_nonce'); // Process the update if we have valid data if (isset($_POST['intent_id'], $_POST['intent_threshold'])) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; $intent_id = intval($_POST['intent_id']); $threshold_percentage = max(70, min(95, intval($_POST['intent_threshold']))); $similarity_threshold = $threshold_percentage / 100; $result = $wpdb->update( $table_name, ['similarity_threshold' => $similarity_threshold], ['id' => $intent_id], ['%f'], ['%d'] ); // Handle AJAX requests if (wp_doing_ajax()) { if ($result === false) { wp_send_json_error(array('message' => 'Failed to update threshold')); } else { wp_send_json_success(array('threshold' => $threshold_percentage)); } return; } } // Redirect for regular form submissions wp_safe_redirect(admin_url('admin.php?page=mxchat-actions&updated=true')); exit; } /** * Enhanced get_available_callbacks function with form action exclusion * * @param bool $grouped Whether to return callbacks grouped by category * @param bool $include_all Whether to include all potential actions (even if add-on not installed) * @return array Callbacks data with icons, descriptions and availability status */ private function mxchat_get_available_callbacks($grouped = false, $include_all = true) { // Load WordPress plugin functions if needed if (!function_exists('get_plugins')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } // Get active plugins $active_plugins = get_option('active_plugins', array()); // Functions to exclude from the action selector $excluded_functions = array( 'mxchat_handle_form_collection' // Exclude form actions ); // Define add-on plugin files and their corresponding action functions $addon_plugins = array( 'mxchat-woo/mxchat-woo.php' => array( 'functions' => array( 'mxchat_handle_product_recommendations', 'mxchat_handle_order_history', 'mxchat_show_product_card', 'mxchat_add_to_cart', 'mxchat_checkout_redirect' ), 'name' => __('WooCommerce Add-on', 'mxchat'), 'pro_required' => true ), 'mxchat-perplexity/mxchat-perplexity.php' => array( 'functions' => array('mxchat_perplexity_research'), 'name' => __('Perplexity Add-on', 'mxchat'), 'pro_required' => true ), // Add other add-ons and their functions here ); // Get the functions that are provided by active add-ons $addon_provided_functions = array(); $addon_function_mapping = array(); // Maps functions to their add-on info // Check which add-ons are active foreach ($addon_plugins as $plugin_file => $addon_info) { $is_active = in_array($plugin_file, $active_plugins); // For each function in this addon foreach ($addon_info['functions'] as $function) { // Consider a function installed only if: // 1. The add-on is active AND // 2. Either it doesn't require Pro OR Pro is activated $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated); // If the add-on is installed, mark this function as provided by an add-on if ($is_installed) { $addon_provided_functions[] = $function; } // Store addon info for this function regardless of installation status $addon_function_mapping[$function] = array( 'addon' => basename(dirname($plugin_file)), 'addon_name' => $addon_info['name'], 'pro_required' => $addon_info['pro_required'], 'is_active' => $is_active, 'is_installed' => $is_installed ); } } // Core callbacks - always available in the base plugin $core_callbacks = array( 'mxchat_handle_email_capture' => array( 'label' => __('Loops Email Capture', 'mxchat'), 'pro_only' => false, 'group' => __('Customer Engagement', 'mxchat'), 'icon' => 'email-alt', 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'), 'addon' => false, // Not from an add-on 'installed' => true // Always installed with base plugin ), 'mxchat_handle_search_request' => array( 'label' => __('Brave Web Search', 'mxchat'), 'pro_only' => false, 'group' => __('Search Features', 'mxchat'), 'icon' => 'search', 'description' => __('Let users search the web directly from the chat', 'mxchat'), 'addon' => false, 'installed' => true ), 'mxchat_handle_image_search_request' => array( 'label' => __('Brave Image Search', 'mxchat'), 'pro_only' => false, 'group' => __('Search Features', 'mxchat'), 'icon' => 'format-image', 'description' => __('Search and display images in the chat conversation', 'mxchat'), 'addon' => false, 'installed' => true ), // Pro core features - check is_activated property 'mxchat_generate_image' => array( 'label' => __('Generate Image', 'mxchat'), 'pro_only' => false, 'group' => __('Other Features', 'mxchat'), 'icon' => 'art', 'description' => __('Create images with DALL-E 3 from OpenAI (requires OpenAI API key)', 'mxchat'), 'addon' => false, 'installed' => $this->is_activated ), 'mxchat_handle_pdf_discussion' => array( 'label' => __('Chat with PDF', 'mxchat'), 'pro_only' => true, 'group' => __('Other Features', 'mxchat'), 'icon' => 'media-document', 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'), 'addon' => false, 'installed' => $this->is_activated ), 'mxchat_live_agent_handover' => array( 'label' => __('Slack Live Agent', 'mxchat'), 'pro_only' => true, 'group' => __('Customer Engagement', 'mxchat'), 'icon' => 'admin-users', 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'), 'addon' => false, 'installed' => $this->is_activated ), 'mxchat_handle_switch_to_chatbot_intent' => array( 'label' => __('Back to Chatbot', 'mxchat'), 'pro_only' => true, 'group' => __('Customer Engagement', 'mxchat'), 'icon' => 'backup', 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'), 'addon' => false, 'installed' => $this->is_activated ), ); // Add-on callbacks with placeholders - only include if the add-on is NOT active $addon_callbacks = array( // WooCommerce Add-on 'mxchat_handle_product_recommendations' => array( 'label' => __('Product Recommendations', 'mxchat'), 'pro_only' => true, 'group' => __('WooCommerce Features', 'mxchat'), 'icon' => 'cart', 'description' => __('Suggest products based on customer preferences', 'mxchat'), ), 'mxchat_handle_order_history' => array( 'label' => __('Order History', 'mxchat'), 'pro_only' => true, 'group' => __('WooCommerce Features', 'mxchat'), 'icon' => 'clipboard', 'description' => __('Allow customers to check their order status', 'mxchat'), ), 'mxchat_show_product_card' => array( 'label' => __('Show Product Card', 'mxchat'), 'pro_only' => true, 'group' => __('WooCommerce Features', 'mxchat'), 'icon' => 'products', 'description' => __('Display product information in the chat', 'mxchat'), ), 'mxchat_add_to_cart' => array( 'label' => __('Add to Cart', 'mxchat'), 'pro_only' => true, 'group' => __('WooCommerce Features', 'mxchat'), 'icon' => 'plus-alt', 'description' => __('Add products to cart directly from chat', 'mxchat'), ), 'mxchat_checkout_redirect' => array( 'label' => __('Proceed to Checkout', 'mxchat'), 'pro_only' => true, 'group' => __('WooCommerce Features', 'mxchat'), 'icon' => 'arrow-right-alt', 'description' => __('Redirect customer to checkout page', 'mxchat'), ), // Perplexity Add-on 'mxchat_perplexity_research' => array( 'label' => __('Perplexity Research', 'mxchat'), 'pro_only' => true, 'group' => __('Search Features', 'mxchat'), 'icon' => 'book-alt', 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'), ), ); // Enhance add-on callbacks with installation status and addon info foreach ($addon_callbacks as $function => $data) { if (isset($addon_function_mapping[$function])) { $addon_info = $addon_function_mapping[$function]; $addon_callbacks[$function]['addon'] = $addon_info['addon']; $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name']; $addon_callbacks[$function]['installed'] = $addon_info['is_installed']; // Set pro_only based on add-on configuration $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required']; } else { $addon_callbacks[$function]['addon'] = 'unknown'; $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat'); $addon_callbacks[$function]['installed'] = false; } } // Initialize callbacks with core features $callbacks = $core_callbacks; // Get callbacks from active add-ons $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array()); // Add placeholder callbacks only for add-ons that aren't active if ($include_all) { foreach ($addon_callbacks as $function => $data) { // Skip placeholders for functions provided by active add-ons if (in_array($function, $addon_provided_functions)) { continue; } // Skip excluded functions if (in_array($function, $excluded_functions)) { continue; } // Add the placeholder $callbacks[$function] = $data; } } // Add callbacks from active add-ons (will override placeholders) foreach ($active_addon_callbacks as $function => $data) { // Skip excluded functions if (in_array($function, $excluded_functions)) { continue; } // Always include callbacks from add-ons $callbacks[$function] = $data; // Ensure they have the proper add-on info if (isset($addon_function_mapping[$function])) { $addon_info = $addon_function_mapping[$function]; $callbacks[$function]['addon'] = $addon_info['addon']; $callbacks[$function]['addon_name'] = $addon_info['addon_name']; $callbacks[$function]['installed'] = $addon_info['is_installed']; $callbacks[$function]['pro_only'] = $addon_info['pro_required']; } } // Just before returning callbacks, sort them to prioritize free features if (!$grouped) { // Create temporary arrays for sorting $free_callbacks = array(); $pro_callbacks = array(); // Split callbacks into free and pro foreach ($callbacks as $key => $data) { if (isset($data['pro_only']) && $data['pro_only']) { $pro_callbacks[$key] = $data; } else { $free_callbacks[$key] = $data; } } // Merge with free callbacks first $callbacks = array_merge($free_callbacks, $pro_callbacks); } // Return grouped structure if requested if ($grouped) { $grouped_callbacks = array(); foreach ($callbacks as $key => $data) { $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat'); // Ensure we carry forward all the new fields in grouped mode $callback_data = array( 'label' => $data['label'], 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false, 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic', 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'), 'addon' => isset($data['addon']) ? $data['addon'] : false, 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '', 'installed' => isset($data['installed']) ? $data['installed'] : true ); $grouped_callbacks[$group_label][$key] = $callback_data; } // Sort within each group to prioritize free features foreach ($grouped_callbacks as $group => $items) { $free_items = array(); $pro_items = array(); foreach ($items as $key => $data) { if (isset($data['pro_only']) && $data['pro_only']) { $pro_items[$key] = $data; } else { $free_items[$key] = $data; } } $grouped_callbacks[$group] = array_merge($free_items, $pro_items); } return $grouped_callbacks; } return $callbacks; } private function mxchat_average_vectors($vectors) { $vector_length = count($vectors[0]); $sum_vector = array_fill(0, $vector_length, 0); foreach ($vectors as $vector) { for ($i = 0; $i < $vector_length; $i++) { $sum_vector[$i] += $vector[$i]; } } // Divide each component by the number of vectors to get the average $num_vectors = count($vectors); for ($i = 0; $i < $vector_length; $i++) { $sum_vector[$i] /= $num_vectors; } return $sum_vector; } public function mxchat_handle_delete_intent() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__('Unauthorized user', 'mxchat') ); } check_admin_referer('mxchat_delete_intent_nonce'); if (isset($_POST['intent_id'])) { global $wpdb; $table_name = $wpdb->prefix . 'mxchat_intents'; $intent_id = intval($_POST['intent_id']); $wpdb->delete($table_name, ['id' => $intent_id], ['%d']); } wp_safe_redirect(admin_url('admin.php?page=mxchat-actions')); exit; } public function mxchat_page_init() { // Register settings register_setting( 'mxchat_option_group', 'mxchat_options', array($this, 'mxchat_sanitize') ); register_setting( 'mxchat_option_group', 'mxchat_similarity_threshold', array( 'type' => 'number', 'sanitize_callback' => function($value) { $value = absint($value); // Ensure it's an integer return min(max($value, 70), 85); // Enforce range }, 'default' => 80, ) ); // Chatbot Settings Section add_settings_section( 'mxchat_chatbot_section', esc_html__('Chatbot Settings', 'mxchat'), null, 'mxchat-chatbot' ); // Similarity Threshold Slider add_settings_field( 'similarity_threshold', // Field ID esc_html__('Similarity Threshold', 'mxchat'), // Field title array($this, 'mxchat_similarity_threshold_callback'), // Callback function 'mxchat-chatbot', // Page 'mxchat_chatbot_section' // Section ); add_settings_field( 'append_to_body', esc_html__('Auto-Display Chatbot', 'mxchat'), array($this, 'mxchat_append_to_body_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); // Existing fields... add_settings_field( 'api_key', esc_html__('OpenAI API Key', 'mxchat'), array($this, 'api_key_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section', array( 'class' => 'mxchat-setting-row', 'data-provider' => 'openai' ) ); add_settings_field( 'xai_api_key', esc_html__('X.AI API Key', 'mxchat'), array($this, 'xai_api_key_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section', array( 'class' => 'mxchat-setting-row', 'data-provider' => 'xai' ) ); add_settings_field( 'claude_api_key', esc_html__('Claude API Key', 'mxchat'), array($this, 'claude_api_key_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section', array( 'class' => 'mxchat-setting-row', 'data-provider' => 'claude' ) ); add_settings_field( 'deepseek_api_key', esc_html__('DeepSeek API Key', 'mxchat'), array($this, 'deepseek_api_key_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section', array( 'class' => 'mxchat-setting-row', 'data-provider' => 'deepseek' ) ); add_settings_field( 'gemini_api_key', esc_html__('Google Gemini API Key', 'mxchat'), array($this, 'gemini_api_key_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section', array( 'class' => 'mxchat-setting-row', 'data-provider' => 'gemini' ) ); add_settings_field( 'voyage_api_key', esc_html__('Voyage AI API Key', 'mxchat'), array($this, 'voyage_api_key_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section', array( 'class' => 'mxchat-setting-row', 'data-provider' => 'voyage' ) ); add_settings_field( 'model', esc_html__('Chat Model', 'mxchat'), array($this, 'mxchat_model_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); // Add the settings field add_settings_field( 'embedding_model', esc_html__('Embedding Model', 'mxchat'), array($this, 'embedding_model_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'system_prompt_instructions', esc_html__('AI Instructions (Behavior)', 'mxchat'), array($this, 'system_prompt_instructions_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'top_bar_title', esc_html__('Top Bar Title', 'mxchat'), array($this, 'mxchat_top_bar_title_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'ai_agent_text', esc_html__('AI Agent Text', 'mxchat'), array($this, 'mxchat_ai_agent_text_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'enable_email_block', esc_html__('Require Email To Chat', 'mxchat'), array($this, 'enable_email_block_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'email_blocker_header_content', esc_html__('Require Email Chat Content', 'mxchat'), array($this, 'email_blocker_header_content_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'email_blocker_button_text', esc_html__('Require Email Chat Button Text', 'mxchat'), [$this, 'email_blocker_button_text_callback'], 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'intro_message', esc_html__('Introductory Message', 'mxchat'), array($this, 'mxchat_intro_message_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'input_copy', esc_html__('Input Copy', 'mxchat'), array($this, 'mxchat_input_copy_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'rate_limit_logged_out', __('Rate Limit for Logged-out Users', 'mxchat'), array($this, 'mxchat_rate_limit_logged_out_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'rate_limit_roles', __('Rate Limits by User Role', 'mxchat'), array($this, 'mxchat_rate_limit_roles_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'rate_limit_message', esc_html__('Rate Limit Message', 'mxchat'), array($this, 'mxchat_rate_limit_message_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'pre_chat_message', esc_html__('Chat Teaser Pop-up', 'mxchat'), array($this, 'mxchat_pre_chat_message_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'privacy_toggle', esc_html__('Toggle Privacy Notice', 'mxchat'), array($this, 'mxchat_privacy_toggle_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'complianz_toggle', esc_html__('Enable Complianz', 'mxchat'), array($this, 'mxchat_complianz_toggle_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'link_target_toggle', esc_html__('Open Links in a New Tab', 'mxchat'), array($this, 'mxchat_link_target_toggle_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'chat_persistence_toggle', esc_html__('Enable Chat Persistence', 'mxchat'), array($this, 'mxchat_chat_persistence_toggle_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'popular_question_1', esc_html__('Quick Question 1', 'mxchat'), array($this, 'mxchat_popular_question_1_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'popular_question_2', esc_html__('Quick Question 2', 'mxchat'), array($this, 'mxchat_popular_question_2_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'popular_question_3', esc_html__('Quick Question 3', 'mxchat'), array($this, 'mxchat_popular_question_3_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); add_settings_field( 'additional_popular_questions', esc_html__('Additional Quick Questions', 'mxchat'), array($this, 'mxchat_additional_popular_questions_callback'), 'mxchat-chatbot', 'mxchat_chatbot_section' ); // Loops Settings Section add_settings_section( 'mxchat_loops_section', esc_html__('Loops Settings', 'mxchat'), null, 'mxchat-embed' ); // Loops Settings Fields add_settings_field( 'loops_api_key', esc_html__('Loops API Key', 'mxchat'), array($this, 'mxchat_loops_api_key_callback'), 'mxchat-embed', 'mxchat_loops_section' ); add_settings_field( 'loops_mailing_list', esc_html__('Loops Mailing List', 'mxchat'), array($this, 'mxchat_loops_mailing_list_callback'), 'mxchat-embed', 'mxchat_loops_section' ); add_settings_field( 'triggered_phrase_response', esc_html__('Triggered Phrase Response', 'mxchat'), array($this, 'mxchat_triggered_phrase_response_callback'), 'mxchat-embed', 'mxchat_loops_section' ); add_settings_field( 'email_capture_response', esc_html__('Email Capture Response', 'mxchat'), array($this, 'mxchat_email_capture_response_callback'), 'mxchat-embed', 'mxchat_loops_section' ); // Brave Search Settings Fields add_settings_section( 'mxchat_brave_section', __('Brave Search Settings', 'mxchat'), array($this, 'mxchat_brave_section_callback'), 'mxchat-embed' ); add_settings_field( 'brave_api_key', __('Brave API Key', 'mxchat'), array($this, 'mxchat_brave_api_key_callback'), 'mxchat-embed', 'mxchat_brave_section' ); add_settings_field( 'brave_image_count', __('Number of Images to Return', 'mxchat'), array($this, 'mxchat_brave_image_count_callback'), 'mxchat-embed', 'mxchat_brave_section' ); add_settings_field( 'brave_safe_search', __('Safe Search', 'mxchat'), array($this, 'mxchat_brave_safe_search_callback'), 'mxchat-embed', 'mxchat_brave_section' ); add_settings_field( 'brave_news_count', __('Number of News Articles', 'mxchat'), array($this, 'mxchat_brave_news_count_callback'), 'mxchat-embed', 'mxchat_brave_section' ); add_settings_field( 'brave_country', __('Country', 'mxchat'), array($this, 'mxchat_brave_country_callback'), 'mxchat-embed', 'mxchat_brave_section' ); add_settings_field( 'brave_language', __('Language', 'mxchat'), array($this, 'mxchat_brave_language_callback'), 'mxchat-embed', 'mxchat_brave_section' ); // Chat with PDF Intent Settings Fields add_settings_section( 'mxchat_pdf_intent_section', __('Toolbar Settings & Intents', 'mxchat'), array($this, 'mxchat_pdf_intent_section_callback'), 'mxchat-embed' ); add_settings_field( 'chat_toolbar_toggle', __('Show Chat Toolbar', 'mxchat'), array($this, 'mxchat_chat_toolbar_toggle_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); // PDF Upload Button Toggle add_settings_field( 'show_pdf_upload_button', __('Show PDF Upload Button', 'mxchat'), array($this, 'mxchat_show_pdf_upload_button_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); // Word Upload Button Toggle add_settings_field( 'show_word_upload_button', __('Show Word Upload Button', 'mxchat'), array($this, 'mxchat_show_word_upload_button_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); add_settings_field( 'pdf_intent_trigger_text', __('Intent Trigger Text', 'mxchat'), array($this, 'mxchat_pdf_intent_trigger_text_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); add_settings_field( 'pdf_intent_success_text', __('Success Text', 'mxchat'), array($this, 'mxchat_pdf_intent_success_text_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); add_settings_field( 'pdf_intent_error_text', __('Error Text', 'mxchat'), array($this, 'mxchat_pdf_intent_error_text_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); // Add PDF Maximum Pages Field add_settings_field( 'pdf_max_pages', __('Maximum Document Pages', 'mxchat'), array($this, 'mxchat_pdf_max_pages_callback'), 'mxchat-embed', 'mxchat_pdf_intent_section' ); // Live Agent Settings Fields add_settings_section( 'mxchat_live_agent_section', __('Live Agent Settings', 'mxchat'), array($this, 'mxchat_live_agent_section_callback'), 'mxchat-embed' ); // Live Agent Status Fields (add at top of live agent settings) add_settings_field( 'live_agent_status', __('Live Agent Status', 'mxchat'), array($this, 'mxchat_live_agent_status_callback'), 'mxchat-embed', 'mxchat_live_agent_section' ); add_settings_field( 'live_agent_notification_message', __('Notification Message', 'mxchat'), array($this, 'mxchat_live_agent_notification_message_callback'), 'mxchat-embed', 'mxchat_live_agent_section' ); add_settings_field( 'live_agent_away_message', __('Away Message', 'mxchat'), array($this, 'mxchat_live_agent_away_message_callback'), 'mxchat-embed', 'mxchat_live_agent_section' ); add_settings_field( 'live_agent_webhook_url', __('Slack Webhook URL', 'mxchat'), array($this, 'mxchat_live_agent_webhook_url_callback'), 'mxchat-embed', 'mxchat_live_agent_section' ); add_settings_field( 'live_agent_secret_key', __('Slack Secret Key', 'mxchat'), array($this, 'mxchat_live_agent_secret_key_callback'), 'mxchat-embed', 'mxchat_live_agent_section' ); // Live Agent Integration Fields add_settings_field( 'live_agent_bot_token', __('Slack Bot OAuth Token', 'mxchat'), array($this, 'mxchat_live_agent_bot_token_callback'), 'mxchat-embed', 'mxchat_live_agent_section' ); // General Settings Section add_settings_section( 'mxchat_general_section', esc_html__('YouTube Tutorials', 'mxchat'), null, 'mxchat-general' ); } public function mxchat_prompts_page_init() { // Register all settings as a single array register_setting( 'mxchat_prompts_options', 'mxchat_prompts_options', array( 'type' => 'array', 'description' => __('MXChat Knowledge Base Settings', 'mxchat'), 'default' => array( 'mxchat_auto_sync_posts' => 0, 'mxchat_auto_sync_pages' => 0, 'mxchat_use_pinecone' => 0, 'mxchat_pinecone_api_key' => '', 'mxchat_pinecone_environment' => '', 'mxchat_pinecone_index' => '', 'mxchat_pinecone_host' => '', // Add this line ), 'sanitize_callback' => array($this, 'sanitize_prompts_options'), ) ); // Add settings saved message add_action('admin_notices', array($this, 'sync_settings_notice')); } /** * Sanitize all prompts options * * @param array $input The unsanitized options array * @return array The sanitized options array */ public function sanitize_prompts_options($input) { // Log the incoming input. //error_log('Sanitizing inputs: ' . print_r($input, true)); $sanitized = array(); // Boolean options $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0; $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0; $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0; // API Key: if less than 32 characters, flag as invalid. $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? ''); if (!empty($api_key) && strlen($api_key) < 32) { add_settings_error( 'mxchat_prompts_options', 'invalid_api_key', __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat') ); $existing_options = get_option('mxchat_prompts_options', array()); $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? ''; } else { $sanitized['mxchat_pinecone_api_key'] = $api_key; } // Environment and Index Name $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? ''); $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? ''); // Host: Remove protocol and validate format. $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? ''); $host = preg_replace('#^https?://#', '', $host); //error_log('Host after removing protocol: ' . $host); if (!empty($host)) { if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) { add_settings_error( 'mxchat_prompts_options', 'invalid_host', __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat') ); $existing_options = get_option('mxchat_prompts_options', array()); $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? ''; } else { $sanitized['mxchat_pinecone_host'] = $host; } } else { $sanitized['mxchat_pinecone_host'] = ''; } //error_log('Final sanitized array: ' . print_r($sanitized, true)); return $sanitized; } public function sync_settings_notice() { // Only show notice on our plugin page if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') { return; } // Check if settings were updated if (isset($_GET['settings-updated'])) { ?>

'software-api', 'request' => 'activation', 'email' => $customer_email, 'license_key' => $license_key, 'product_id' => $product_id ), 'http://mxchat.ai/' ) ); if (is_wp_error($response)) { wp_send_json_error(esc_html__('Activation failed due to a server error: ', 'mxchat') . $response->get_error_message()); return; } $body = wp_remote_retrieve_body($response); $data = json_decode($body); if ($data && isset($data->activated) && $data->activated) { update_option('mxchat_license_status', 'active'); update_option('mxchat_pro_email', $customer_email); update_option('mxchat_activation_key', $license_key); wp_send_json_success(array('message' => esc_html__('License activated successfully', 'mxchat'))); } else { $error_message = isset($data->error) ? $data->error : esc_html__('Activation failed', 'mxchat'); update_option('mxchat_license_status', 'inactive'); update_option('mxchat_license_error', $error_message); wp_send_json_error($error_message); } } public function mxchat_rate_limit_logged_out_callback() { // Load the entire 'mxchat_options' array $all_options = get_option('mxchat_options', []); // Retrieve the saved rate limit or use the default value $default_rate_limit = '10'; $selected_rate_limit = isset($all_options['rate_limit_logged_out']) ? $all_options['rate_limit_logged_out'] : $default_rate_limit; // Define available rate limits $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Output the dropdown echo '
'; echo ''; echo '

' . esc_html__('Set the maximum number of messages a logged-out user can send within a 24-hour period.', 'mxchat') . '

'; echo '
'; } // Add this new callback function public function mxchat_rate_limit_roles_callback() { $all_options = get_option('mxchat_options', []); $roles = wp_roles()->get_names(); $rate_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); echo '
'; foreach ($roles as $role_id => $role_name) { $default_rate_limit = '100'; $selected_rate_limit = isset($all_options['role_rate_limits'][$role_id]) ? $all_options['role_rate_limits'][$role_id] : $default_rate_limit; echo '
'; echo ''; echo ''; echo '
'; } echo '

' . esc_html__('Set the maximum number of messages users in each role can send within a 24-hour period.', 'mxchat') . '

'; echo '
'; } public function mxchat_rate_limit_message_callback() { // Load the entire 'mxchat_options' array $all_options = get_option('mxchat_options', []); // Retrieve the saved message or use the default value $default_message = esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'); $rate_limit_message = isset($all_options['rate_limit_message']) ? $all_options['rate_limit_message'] : $default_message; // Output the textarea echo '
'; printf( '', esc_textarea($rate_limit_message) ); echo '

' . esc_html__('This message will be displayed when a user exceeds the rate limit.', 'mxchat') . '

'; echo '
'; } private function mxchat_add_option_field($id, $title, $callback = '') { add_settings_field( $id, __($title, 'mxchat'), $callback ? array($this, $callback) : array($this, $id . '_callback'), 'mxchat-max', 'mxchat_setting_section_id', $id === 'model' ? ['label_for' => 'model'] : [] ); } // OpenAI API Key public function api_key_callback() { $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : ''; echo '
'; echo ''; echo ''; echo '

' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '

'; echo '
'; } // X.AI API Key public function xai_api_key_callback() { $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : ''; echo '
'; echo ''; echo ''; echo '

' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '

'; echo '
'; } // Claude API Key public function claude_api_key_callback() { $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : ''; echo '
'; echo ''; echo ''; echo '

' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '

'; echo '
'; } // DeepSeek API Key public function deepseek_api_key_callback() { $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : ''; echo '
'; echo ''; echo ''; echo '

' . esc_html__('Required for your selected chat model. Important: You must add credits before use.', 'mxchat') . '

'; echo '
'; } // Gemini API Key public function gemini_api_key_callback() { $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : ''; echo '
'; echo ''; echo ''; echo '

' . esc_html__('Required for Google Gemini models. Get your API key from Google AI Studio.', 'mxchat') . '

'; echo '
'; } // Voyage API Key public function voyage_api_key_callback() { $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : ''; echo '
'; echo ''; echo ''; echo '

' . esc_html__('Required for your selected embedding model. Important: You must add credits before use.', 'mxchat') . '

'; echo '
'; } public function mxchat_loops_api_key_callback() { $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : ''; // Hidden fields to "trap" autofill echo ''; echo ''; echo '
'; echo sprintf( '', $loops_api_key ); echo ''; echo '
'; echo '

' . esc_html__('Enter your Loops API Key here. Once entered, refreshed page to load list (See FAQ for details)', 'mxchat') . '

'; } public function mxchat_loops_mailing_list_callback() { // Add error handling and type checking $loops_api_key = ''; $selected_list = ''; // Safely get the API key if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) { $loops_api_key = $this->options['loops_api_key']; } // Safely get the selected list if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) { $selected_list = $this->options['loops_mailing_list']; } if (!empty($loops_api_key)) { $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key); if (is_array($lists) && !empty($lists)) { echo ''; } else { echo '

' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '

'; } } else { echo '

' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '

'; } } public function mxchat_triggered_phrase_response_callback() { $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'); $triggered_response = isset($this->options['triggered_phrase_response']) ? $this->options['triggered_phrase_response'] : $default_response; echo sprintf( '', esc_textarea($triggered_response) ); echo '

' . esc_html__('Enter the chatbot response when a trigger keyword is detected, prompting the user to share their email.', 'mxchat') . '

'; } public function mxchat_email_capture_response_callback() { $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'); $email_capture_response = isset($this->options['email_capture_response']) ? $this->options['email_capture_response'] : $default_response; echo sprintf( '', esc_textarea($email_capture_response) ); echo '

' . esc_html__('Enter the message to send when a user provides their email.', 'mxchat') . '

'; } public function mxchat_pre_chat_message_callback() { // Load the entire 'mxchat_options' array $all_options = get_option('mxchat_options', []); // Retrieve the saved message or use the default value $default_message = __('Hey there! Ask me anything!', 'mxchat'); $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message; // Output the textarea printf( '', esc_textarea($pre_chat_message) ); echo '

' . esc_html__('Set the message displayed to users before they start a chat. Use this to provide a friendly greeting or instructions.', 'mxchat') . '

'; } // Callback for AI Instructions textarea public function system_prompt_instructions_callback() { // Retrieve the current value of the system prompt instructions $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : ''; // Render the textarea field printf( '', $instructions ); // Provide a helpful description echo '

' . esc_html__('Provide system-level instructions for the AI to guide its behavior. Be clear and concise for better results.', 'mxchat') . '

'; } public function mxchat_model_callback() { // Define available models grouped by provider $models = array( esc_html__('Google Gemini Models', 'mxchat') => array( 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Next-Gen Features)', 'mxchat'), 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Cost-Efficient)', 'mxchat'), 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Complex Reasoning)', 'mxchat'), 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Fast & Versatile)', 'mxchat'), ), esc_html__('X.AI Models', 'mxchat') => array( 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'), 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'), 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'), 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'), 'grok-2' => esc_html__('Grok 2', 'mxchat') ), esc_html__('DeepSeek Models', 'mxchat') => array( 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'), ), esc_html__('Claude Models', 'mxchat') => array( 'claude-3-7-sonnet-20250219' => esc_html__('Claude 3.7 Sonnet (Most Intelligent)', 'mxchat'), 'claude-3-5-sonnet-20241022' => esc_html__('Claude 3.5 Sonnet (Intelligent)', 'mxchat'), 'claude-3-opus-20240229' => esc_html__('Claude 3 Opus (Highly Complex Tasks)', 'mxchat'), 'claude-3-sonnet-20240229' => esc_html__('Claude 3 Sonnet (Balanced)', 'mxchat'), 'claude-3-haiku-20240307' => esc_html__('Claude 3 Haiku (Fastest)', 'mxchat') ), esc_html__('OpenAI Models', 'mxchat') => array( 'gpt-4.1-2025-04-14' => esc_html__('GPT-4.1 (Flagship for Complex Tasks)', 'mxchat'), 'gpt-4o' => esc_html__('GPT-4o (Recommended)', 'mxchat'), 'gpt-4o-mini' => esc_html__('GPT-4o Mini (Fast and Lightweight)', 'mxchat'), 'gpt-4-turbo' => esc_html__('GPT-4 Turbo (High-Performance)', 'mxchat'), 'gpt-4' => esc_html__('GPT-4 (High Intelligence)', 'mxchat'), 'gpt-3.5-turbo' => esc_html__('GPT-3.5 Turbo (Affordable and Fast)', 'mxchat') ) ); // Retrieve the currently selected model from saved options $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-4o'; // Begin the select dropdown echo ''; // Updated description to remove mention of Pro-only models echo '

' . esc_html__('Select the AI model your chatbot will use for chatting.', 'mxchat') . '

'; } // Callback function for embedding model selection public function embedding_model_callback() { $models = array( esc_html__('OpenAI Embeddings', 'mxchat') => array( 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'), 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'), 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'), ), esc_html__('Voyage AI Embeddings', 'mxchat') => array( 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'), ) ); $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002'; echo ''; echo '

IMPORTANT: 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.

'; } public function mxchat_top_bar_title_callback() { // Retrieve the current value of the top bar title from saved options $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : ''; // Render the input field echo ''; // Add a description echo '

' . esc_html__('Enter the title text that will appear on the top bar of the chatbot.', 'mxchat') . '

'; } public function mxchat_ai_agent_text_callback() { // Retrieve the current value of the AI agent text from saved options $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : ''; // Render the input field echo ''; // Add a description echo '

' . esc_html__('Enter the text that will appear for AI agents in the status indicator. Default: "AI Agent"', 'mxchat') . '

'; } public function enable_email_block_callback() { // Load full plugin options array $all_options = get_option('mxchat_options', []); // Get the value, default to 'off' $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off'; // Check if it's 'on' $checked = ($enable_email_block === 'on') ? 'checked' : ''; echo ''; echo '

' . 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') . '

'; } public function email_blocker_header_content_callback() { // Load the entire 'mxchat_options' array $all_options = get_option('mxchat_options', []); // Retrieve the saved content or default to empty $content = isset($all_options['email_blocker_header_content']) ? $all_options['email_blocker_header_content'] : ''; // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content" echo ''; echo '

'; echo esc_html__('You may enter HTML here, such as <h2>Welcome</h2> or <p>Let\'s get started</p>.', 'mxchat'); echo '

'; } public function email_blocker_button_text_callback() { // Load the entire 'mxchat_options' array $all_options = get_option('mxchat_options', []); // Retrieve the saved button text or default to empty $button_text = isset($all_options['email_blocker_button_text']) ? $all_options['email_blocker_button_text'] : ''; // Use esc_attr to safely render the existing text echo ''; echo '

'; echo esc_html__('Enter the text you want on the submit button, e.g. "Start Chat".', 'mxchat'); echo '

'; } public function mxchat_intro_message_callback() { // Load the entire 'mxchat_options' array $all_options = get_option('mxchat_options', []); // Retrieve the saved intro message or use the default $default_message = __('Hello! How can I assist you today?', 'mxchat'); $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message; // Output the textarea with the saved value without escaping HTML ?>

', esc_attr($input_copy), esc_attr__('How can I assist?', 'mxchat') ); // Output the description echo '

' . esc_html__('This is the placeholder text for the chat input field.', 'mxchat') . '

'; } public function mxchat_user_message_font_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['user_message_font_color']) ? esc_attr($this->options['user_message_font_color']) : '#ffffff', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_bot_message_bg_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['bot_message_bg_color']) ? esc_attr($this->options['bot_message_bg_color']) : '#e1e1e1', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_bot_message_font_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['bot_message_font_color']) ? esc_attr($this->options['bot_message_font_color']) : '#333333', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_live_agent_message_bg_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['live_agent_message_bg_color']) ? esc_attr($this->options['live_agent_message_bg_color']) : '#ffffff', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_live_agent_message_font_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['live_agent_message_font_color']) ? esc_attr($this->options['live_agent_message_font_color']) : '#333333', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_mode_indicator_bg_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['mode_indicator_bg_color']) ? esc_attr($this->options['mode_indicator_bg_color']) : '#767676', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_mode_indicator_font_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['mode_indicator_font_color']) ? esc_attr($this->options['mode_indicator_font_color']) : '#ffffff', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_toolbar_icon_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['toolbar_icon_color']) ? esc_attr($this->options['toolbar_icon_color']) : '#212121', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_top_bar_bg_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['top_bar_bg_color']) ? esc_attr($this->options['top_bar_bg_color']) : '#00b294', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_send_button_font_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['send_button_font_color']) ? esc_attr($this->options['send_button_font_color']) : '#ffffff', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_chatbot_background_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['chatbot_background_color']) ? esc_attr($this->options['chatbot_background_color']) : '#000000', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_icon_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['icon_color']) ? esc_attr($this->options['icon_color']) : '#ffffff', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_custom_icon_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $custom_icon_url = isset($this->options['custom_icon']) ? esc_url($this->options['custom_icon']) : ''; echo '
'; echo sprintf( '', $custom_icon_url, esc_attr__('Enter PNG URL', 'mxchat'), esc_attr($disabled) ); // Preview container for the icon if (!empty($custom_icon_url)) { echo '
'; echo '' . esc_attr__('Custom Icon Preview', 'mxchat') . ''; echo '
'; } echo '

' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_title_icon_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; // Fixed the variable reference - it was using custom_icon instead of title_icon $title_icon_url = isset($this->options['title_icon']) ? esc_url($this->options['title_icon']) : ''; echo '
'; echo sprintf( '', $title_icon_url, esc_attr__('Enter PNG URL', 'mxchat'), esc_attr($disabled) ); // Preview container for the icon if (!empty($title_icon_url)) { echo '
'; echo '' . esc_attr__('Title Icon Preview', 'mxchat') . ''; echo '
'; } echo '

' . esc_html__('Upload your PNG icon and paste the URL here. Recommended size: 48x48 pixels.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_chat_input_font_color_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; echo sprintf( '', isset($this->options['chat_input_font_color']) ? esc_attr($this->options['chat_input_font_color']) : '#555555', esc_attr($disabled) ); if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_append_to_body_callback() { // Get value from options array, default to 'off' $append_to_body = isset($this->options['append_to_body']) ? $this->options['append_to_body'] : 'off'; $checked = ($append_to_body === 'on') ? 'checked' : ''; echo ''; echo '

' . esc_html__('Show chatbot automatically on all pages. When disabled, you can place the chatbot manually using shortcode [mxchat_chatbot floating="yes"].', 'mxchat') . '

'; } public function mxchat_privacy_toggle_callback() { // Load from mxchat_options array $options = get_option('mxchat_options', []); // Get privacy toggle value with fallback $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off'; $checked = ($privacy_toggle === 'on') ? 'checked' : ''; // Get privacy text with fallback $privacy_text = isset($options['privacy_text']) ? $options['privacy_text'] : __('By chatting, you agree to our privacy policy.', 'mxchat'); // Output the toggle switch echo ''; echo '

' . esc_html__('Enable this option to display a privacy notice below the chat widget.', 'mxchat') . '

'; // Output the custom text input field echo sprintf( '', esc_textarea($privacy_text) ); echo '

' . esc_html__('Enter the privacy policy text. You can include HTML links.', 'mxchat') . '

'; } public function mxchat_complianz_toggle_callback() { // Load from mxchat_options array $options = get_option('mxchat_options', []); // Get complianz toggle value with fallback $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off'; $checked = ($complianz_toggle === 'on') ? 'checked' : ''; // Output the toggle switch echo ''; echo '

' . esc_html__('Enable this option to apply Complianz consent logic to the chatbot (must have Complianz Plugin).', 'mxchat') . '

'; } public function mxchat_link_target_toggle_callback() { // Load from mxchat_options array $options = get_option('mxchat_options', []); // Get link target toggle value with fallback $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off'; $checked = ($link_target_toggle === 'on') ? 'checked' : ''; // Output the toggle switch echo ''; echo '

' . esc_html__('Enable to open links in a new tab (default is to open in the same tab).', 'mxchat') . '

'; } public function mxchat_chat_persistence_toggle_callback() { // Load from mxchat_options array $options = get_option('mxchat_options', []); // Get chat persistence toggle value with fallback $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off'; $checked = ($chat_persistence_toggle === 'on') ? 'checked' : ''; // Output the toggle switch echo ''; echo '

' . esc_html__('Enable to keep chat history when users navigate tabs or return to the site within 24 hours.', 'mxchat') . '

'; } public function mxchat_popular_question_1_callback() { // Load the full plugin options array $all_options = get_option('mxchat_options', []); // Retrieve the specific option for popular_question_1 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : ''; // Render the input field printf( '', esc_attr($popular_question_1), esc_attr__('Enter Quick Question 1', 'mxchat') ); // Add a description for the field echo '

' . esc_html__('This will be the first Quick Question in the chatbot, displayed above the input field.', 'mxchat') . '

'; } public function mxchat_popular_question_2_callback() { // Load the full plugin options array $all_options = get_option('mxchat_options', []); // Retrieve the specific option for popular_question_2 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : ''; // Render the input field printf( '', esc_attr($popular_question_2), esc_attr__('Enter Quick Question 2', 'mxchat') ); // Add a description for the field echo '

' . esc_html__('This will be the second Quick Question in the chatbot.', 'mxchat') . '

'; } public function mxchat_popular_question_3_callback() { // Load the full plugin options array $all_options = get_option('mxchat_options', []); // Retrieve the specific option for popular_question_3 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : ''; // Render the input field printf( '', esc_attr($popular_question_3), esc_attr(__('Enter Quick Question 3', 'mxchat')) ); // Add a description for the field echo '

' . esc_html__('This will be the third Quick Question in the chatbot.', 'mxchat') . '

'; } public function mxchat_additional_popular_questions_callback() { $options = get_option('mxchat_options', []); $additional_questions = isset($options['additional_popular_questions']) ? $options['additional_popular_questions'] : get_option('additional_popular_questions', array()); echo '
'; if (!empty($additional_questions)) { foreach ($additional_questions as $index => $question) { printf( '
', esc_attr($question), esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)), $index, esc_attr(__('Remove question', 'mxchat')), esc_html__('Remove', 'mxchat') ); } } else { printf( '
', esc_attr(__('Enter Additional Quick Question 4', 'mxchat')), esc_attr(__('Remove question', 'mxchat')), esc_html__('Remove', 'mxchat') ); } echo '
'; printf( '', esc_attr(__('Add question', 'mxchat')), esc_html__('Add Question', 'mxchat') ); echo '

' . esc_html__('Add as many Quick Questions as you need.', 'mxchat') . '

'; echo '
'; } public function mxchat_brave_api_key_callback() { $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : ''; echo '
'; echo sprintf( '', $brave_api_key ); echo ''; echo '
'; echo '

' . __('Enter your Brave Search API Key here. (See FAQ for details)', 'mxchat') . '

'; } public function mxchat_brave_image_count_callback() { $brave_image_count = isset($this->options['brave_image_count']) ? intval($this->options['brave_image_count']) : 4; echo sprintf( '', $brave_image_count ); echo '

' . __('Select the number of images to return (1-6).', 'mxchat') . '

'; } public function mxchat_brave_safe_search_callback() { $brave_safe_search = isset($this->options['brave_safe_search']) ? esc_attr($this->options['brave_safe_search']) : 'strict'; echo ''; echo '

' . esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') . '

'; } public function mxchat_brave_news_count_callback() { $brave_news_count = isset($this->options['brave_news_count']) ? intval($this->options['brave_news_count']) : 3; echo sprintf( '', $brave_news_count ); echo '

' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '

'; } public function mxchat_brave_country_callback() { $brave_country = isset($this->options['brave_country']) ? esc_attr($this->options['brave_country']) : 'us'; echo sprintf( '', $brave_country ); echo '

' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '

'; } public function mxchat_brave_language_callback() { $brave_language = isset($this->options['brave_language']) ? esc_attr($this->options['brave_language']) : 'en'; echo sprintf( '', $brave_language ); echo '

' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '

'; } // Section Callback public function mxchat_pdf_intent_section_callback() { echo '

' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '

'; } public function mxchat_chat_toolbar_toggle_callback() { // Get chat toolbar toggle value with fallback $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off'; $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : ''; // Check if the plugin is activated (paid feature) $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; // Output the toggle switch echo ''; // Pro feature overlay if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; echo '

' . 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') . '

'; } /** * Callback for PDF upload button toggle setting */ public function mxchat_show_pdf_upload_button_callback() { // Get toggle value with fallback $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on'; $checked = ($show_pdf_button === 'on') ? 'checked' : ''; // Check if the plugin is activated (paid feature) $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; // Output the toggle switch echo ''; // Pro feature overlay if needed if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; echo '

' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '

'; } /** * Callback for Word upload button toggle setting */ public function mxchat_show_word_upload_button_callback() { // Get toggle value with fallback $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on'; $checked = ($show_word_button === 'on') ? 'checked' : ''; // Check if the plugin is activated (paid feature) $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; // Output the toggle switch echo ''; // Pro feature overlay if needed if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; echo '

' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '

'; } public function mxchat_pdf_intent_trigger_text_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'); echo '
'; echo sprintf( '', esc_attr__('Enter trigger text', 'mxchat'), esc_attr($disabled), isset($this->options['pdf_intent_trigger_text']) ? esc_textarea($this->options['pdf_intent_trigger_text']) : esc_textarea($default_text) ); echo '

' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_pdf_intent_success_text_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat'); echo '
'; echo sprintf( '', esc_attr__('Enter success text', 'mxchat'), esc_attr($disabled), isset($this->options['pdf_intent_success_text']) ? esc_textarea($this->options['pdf_intent_success_text']) : esc_textarea($default_text) ); echo '

' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_pdf_intent_error_text_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'); echo '
'; echo sprintf( '', esc_attr__('Enter error text', 'mxchat'), esc_attr($disabled), isset($this->options['pdf_intent_error_text']) ? esc_textarea($this->options['pdf_intent_error_text']) : esc_textarea($default_text) ); echo '

' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_pdf_max_pages_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69; echo '
'; echo sprintf( '', esc_attr($max_pages), esc_attr($disabled) ); echo '' . esc_html($max_pages) . ''; echo '

' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo ''; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo ''; echo '
'; } echo '
'; } public function mxchat_live_agent_status_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $status = isset($this->options['live_agent_status']) ? $this->options['live_agent_status'] : 'off'; echo '
'; echo ''; echo ''; if (!$this->is_activated) { echo '
'; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo '
'; } echo '
'; } // Away Message Callback public function mxchat_live_agent_away_message_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $message = isset($this->options['live_agent_away_message']) ? $this->options['live_agent_away_message'] : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat'); echo '
'; printf( '', esc_attr($disabled), esc_textarea($message) ); echo '

' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo '
'; } echo '
'; } public function mxchat_live_agent_notification_message_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $message = isset($this->options['live_agent_notification_message']) ? $this->options['live_agent_notification_message'] : __('Live agent has been notified.', 'mxchat'); echo '
'; printf( '', esc_attr($disabled), esc_textarea($message) ); echo '

' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo '
'; } echo '
'; } public function mxchat_live_agent_webhook_url_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; $webhook_url = isset($this->options['live_agent_webhook_url']) ? esc_url($this->options['live_agent_webhook_url']) : esc_url(get_option('live_agent_webhook_url', '')); echo '
'; printf( '', $webhook_url, esc_attr($disabled) ); echo ''; echo '

' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo '
'; } echo '
'; } public function mxchat_similarity_threshold_callback() { // Load from mxchat_options array $options = get_option('mxchat_options', []); // Get value from options array with default of 80 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35; echo '
'; echo sprintf( '', esc_attr($threshold) ); echo sprintf( '%s', esc_html($threshold) ); echo '
'; echo '

'; 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'); echo '

'; } public function mxchat_live_agent_secret_key_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; printf( '', isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : '', esc_attr($disabled) ); echo ''; echo '

' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo '
'; } echo '
'; } public function mxchat_live_agent_bot_token_callback() { $disabled = $this->is_activated ? '' : 'disabled'; $class = $this->is_activated ? 'pro-feature-wrapper active' : 'pro-feature-wrapper inactive'; echo '
'; printf( '', isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : '', esc_attr($disabled) ); echo ''; echo '

' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '

'; if (!$this->is_activated) { echo '
'; echo '' . esc_attr__('Pro Only', 'mxchat') . ''; echo '
'; } echo '
'; } public function mxchat_enqueue_admin_assets() { wp_enqueue_style('wp-color-picker'); // Get the plugin version or file modification time for cache busting $plugin_version = '2.1.6'; // Replace this with your plugin's version // File paths $color_picker_js_path = plugin_dir_path(__FILE__) . '../js/my-color-picker.js'; $embedding_check_js_path = plugin_dir_path(__FILE__) . '../js/embedding-check.js'; $admin_css_path = plugin_dir_path(__FILE__) . '../css/admin-style.css'; $knowledge_css_path = plugin_dir_path(__FILE__) . '../css/knowledge-style.css'; $intent_css_path = plugin_dir_path(__FILE__) . '../css/intent-style.css'; $transcripts_css_path = plugin_dir_path(__FILE__) . '../css/chat-transcripts.css'; $transcripts_js_path = plugin_dir_path(__FILE__) . '../js/mxchat_transcripts.js'; // Check if files exist and get modification times $color_picker_version = file_exists($color_picker_js_path) ? filemtime($color_picker_js_path) : $plugin_version; $embedding_check_version = file_exists($embedding_check_js_path) ? filemtime($embedding_check_js_path) : $plugin_version; $admin_css_version = file_exists($admin_css_path) ? filemtime($admin_css_path) : $plugin_version; $knowledge_css_version = file_exists($knowledge_css_path) ? filemtime($knowledge_css_path) : $plugin_version; $intent_css_version = file_exists($intent_css_path) ? filemtime($intent_css_path) : $plugin_version; $transcripts_css_version = file_exists($transcripts_css_path) ? filemtime($transcripts_css_path) : $plugin_version; $transcripts_js_version = file_exists($transcripts_js_path) ? filemtime($transcripts_js_path) : $plugin_version; $admin_status_js_path = plugin_dir_path(__FILE__) . '../js/admin-status.js'; $admin_status_js_version = file_exists($admin_status_js_path) ? filemtime($admin_status_js_path) : $plugin_version; // Check current admin page $current_page = isset($_GET['page']) ? $_GET['page'] : ''; // Only enqueue on the prompts page if ($current_page === 'mxchat-prompts') { wp_enqueue_script( 'mxchat-status-updater', plugin_dir_url(__FILE__) . '../js/admin-status.js', array('jquery'), $admin_status_js_version, true ); // Add the nonce for the status updater wp_localize_script( 'mxchat-status-updater', 'mxchat_status_data', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('mxchat_status_nonce') ) ); } // Enqueue scripts and styles with corrected paths wp_enqueue_script( 'mxchat-color-picker', plugin_dir_url(__FILE__) . '../js/my-color-picker.js', array('wp-color-picker'), $color_picker_version, true ); wp_enqueue_script( 'mxchat-embedding-check', plugin_dir_url(__FILE__) . '../js/embedding-check.js', array(), $embedding_check_version, true ); wp_enqueue_script( 'mxchat-admin-js', plugin_dir_url(__FILE__) . '../js/mxchat-admin.js', array('jquery'), $plugin_version, true ); wp_localize_script('mxchat-admin-js', 'mxchatAdmin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'), 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'), 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'), 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'), 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'), 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'), 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'), 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'), 'is_activated' => $this->is_activated ? '1' : '0', // Add these two new lines 'status_nonce' => wp_create_nonce('mxchat_status_nonce'), 'status_refresh_interval' => 5000, // Update every 5 seconds )); // Enqueue the admin CSS wp_enqueue_style( 'mxchat-admin-css', plugin_dir_url(__FILE__) . '../css/admin-style.css', array(), $admin_css_version ); // Conditional enqueue for transcripts page if ($current_page === 'mxchat-transcripts') { wp_enqueue_style( 'mxchat-chat-transcripts-css', plugin_dir_url(__FILE__) . '../css/chat-transcripts.css', array(), $transcripts_css_version ); wp_enqueue_script( 'mxchat-transcripts-js', plugin_dir_url(__FILE__) . '../js/mxchat_transcripts.js', array('jquery'), $transcripts_js_version, true ); } // Only enqueue knowledge CSS on knowledge-related pages or all plugin pages if (strpos($current_page, 'mxchat') !== false) { wp_enqueue_style( 'mxchat-knowledge-css', plugin_dir_url(__FILE__) . '../css/knowledge-style.css', array(), $knowledge_css_version ); } // Only enqueue intent-style.css on the mxchat-actions page if ($current_page === 'mxchat-actions') { wp_enqueue_style( 'mxchat-intent-css', plugin_dir_url(__FILE__) . '../css/intent-style.css', array(), $intent_css_version ); } // IMPORTANT: Use the same script handle as above for localizing mxchatPromptsAdmin wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'), )); // Localize the script for color picker and settings wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array( 'ajax_url' => admin_url('admin-ajax.php'), 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off', 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff', 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121', 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121', 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff', 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121', 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121', 'close_button_color' => $this->options['close_button_color'] ?? '#fff', 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121', 'icon_color' => $this->options['icon_color'] ?? '#fff', 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121', 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'), 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'), 'loops_api_key' => $this->options['loops_api_key'] ?? '', 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '', 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'), 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'), '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'), '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'), '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'), 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69, 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '', 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '', 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '', 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff', 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333', 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off', 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on', 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on', 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676', 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff', 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121', )); } public function mxchat_sanitize($input) { $new_input = array(); if (isset($input['api_key'])) { $new_input['api_key'] = sanitize_text_field($input['api_key']); } if (isset($input['similarity_threshold'])) { $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range } if (isset($input['xai_api_key'])) { $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']); } if (isset($input['claude_api_key'])) { $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']); } if (isset($input['deepseek_api_key'])) { $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']); } if (isset($input['gemini_api_key'])) { $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']); } if (isset($input['enable_woocommerce_integration'])) { $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off'; } if (isset($input['privacy_toggle'])) { $new_input['privacy_toggle'] = $input['privacy_toggle']; } if (isset($input['complianz_toggle'])) { $new_input['complianz_toggle'] = $input['complianz_toggle']; } // Handle custom privacy text input if (isset($input['privacy_text'])) { // Allow basic HTML for links $new_input['privacy_text'] = wp_kses_post($input['privacy_text']); } if (isset($input['system_prompt_instructions'])) { $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']); } if (isset($input['mxchat_pro_email'])) { $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']); } if (isset($input['mxchat_activation_key'])) { $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']); } if (isset($input['append_to_body'])) { $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off'; } if (isset($input['top_bar_title'])) { $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']); } if (isset($input['ai_agent_text'])) { $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']); } if (isset($input['enable_email_block'])) { $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']); } if (isset($input['email_blocker_header_content'])) { // wp_kses_post() allows standard HTML tags permitted by WordPress $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']); } if (isset($input['email_blocker_button_text'])) { $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']); } if (isset($input['intro_message'])) { $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead } if (isset($input['input_copy'])) { $new_input['input_copy'] = sanitize_text_field($input['input_copy']); } if (isset($input['rate_limit_message'])) { $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']); } if (isset($input['rate_limit_logged_out'])) { $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); // Add 'unlimited' to allowed values $rate_limit = sanitize_text_field($input['rate_limit_logged_out']); if (in_array($rate_limit, $allowed_limits, true)) { $new_input['rate_limit_logged_out'] = $rate_limit; } else { $new_input['rate_limit_logged_out'] = '10'; // Default for logged-out users } } // Handle role rate limits if (isset($input['role_rate_limits']) && is_array($input['role_rate_limits'])) { $allowed_limits = array('1', '3', '5', '10', '15', '20', '100', 'unlimited'); $new_input['role_rate_limits'] = array(); foreach ($input['role_rate_limits'] as $role_id => $rate_limit) { $rate_limit = sanitize_text_field($rate_limit); if (in_array($rate_limit, $allowed_limits, true)) { $new_input['role_rate_limits'][$role_id] = $rate_limit; } else { $new_input['role_rate_limits'][$role_id] = '100'; // Default } } } if (isset($input['pre_chat_message'])) { $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']); } if (isset($input['voyage_api_key'])) { $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']); } // Add to your sanitize function if (isset($input['embedding_model'])) { $allowed_models = array( 'text-embedding-ada-002', 'text-embedding-3-small', 'text-embedding-3-large', 'voyage-3-large' ); if (in_array($input['embedding_model'], $allowed_models)) { $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']); } } if (isset($input['model'])) { $allowed_models = array( 'gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-pro', 'gemini-1.5-flash', 'grok-3-beta', 'grok-3-fast-beta', 'grok-3-mini-beta', 'grok-3-mini-fast-beta', 'grok-2', 'deepseek-chat', 'claude-3-7-sonnet-20250219', 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-sonnet-20240229', 'claude-3-haiku-20240307', 'gpt-4o', 'gpt-4.1-2025-04-14', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo', ); if (in_array($input['model'], $allowed_models)) { $new_input['model'] = sanitize_text_field($input['model']); } } if (isset($input['close_button_color'])) { $new_input['close_button_color'] = sanitize_hex_color($input['close_button_color']); } if (isset($input['chatbot_bg_color'])) { $new_input['chatbot_bg_color'] = sanitize_hex_color($input['chatbot_bg_color']); } if (isset($input['woocommerce_consumer_key'])) { $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']); } if (isset($input['woocommerce_consumer_secret'])) { $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']); } if (isset($input['user_message_bg_color'])) { $new_input['user_message_bg_color'] = sanitize_hex_color($input['user_message_bg_color']); } if (isset($input['user_message_font_color'])) { $new_input['user_message_font_color'] = sanitize_hex_color($input['user_message_font_color']); } if (isset($input['bot_message_bg_color'])) { $new_input['bot_message_bg_color'] = sanitize_hex_color($input['bot_message_bg_color']); } if (isset($input['bot_message_font_color'])) { $new_input['bot_message_font_color'] = sanitize_hex_color($input['bot_message_font_color']); } if (isset($input['live_agent_message_bg_color'])) { $new_input['live_agent_message_bg_color'] = sanitize_hex_color($input['live_agent_message_bg_color']); } if (isset($input['live_agent_message_font_color'])) { $new_input['live_agent_message_font_color'] = sanitize_hex_color($input['live_agent_message_font_color']); } if (isset($input['mode_indicator_bg_color'])) { $new_input['mode_indicator_bg_color'] = sanitize_hex_color($input['mode_indicator_bg_color']); } if (isset($input['mode_indicator_font_color'])) { $new_input['mode_indicator_font_color'] = sanitize_hex_color($input['mode_indicator_font_color']); } if (isset($input['toolbar_icon_color'])) { $new_input['toolbar_icon_color'] = sanitize_hex_color($input['toolbar_icon_color']); } if (isset($input['top_bar_bg_color'])) { $new_input['top_bar_bg_color'] = sanitize_hex_color($input['top_bar_bg_color']); } if (isset($input['send_button_font_color'])) { $new_input['send_button_font_color'] = sanitize_hex_color($input['send_button_font_color']); } if (isset($input['chatbot_background_color'])) { $new_input['chatbot_background_color'] = sanitize_hex_color($input['chatbot_background_color']); } if (isset($input['icon_color'])) { $new_input['icon_color'] = sanitize_hex_color($input['icon_color']); } if (isset($input['custom_icon'])) { $new_input['custom_icon'] = esc_url_raw($input['custom_icon']); } if (isset($input['title_icon'])) { $new_input['title_icon'] = esc_url_raw($input['title_icon']); } if (isset($input['chat_input_font_color'])) { $new_input['chat_input_font_color'] = sanitize_hex_color($input['chat_input_font_color']); } // Sanitize link_target_toggle if (isset($input['link_target_toggle'])) { $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off'; } // Sanitize Loops API Key if (isset($input['loops_api_key'])) { $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']); } if (isset($input['chat_persistence_toggle'])) { $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off'; } if (isset($input['popular_question_1'])) { $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']); } if (isset($input['popular_question_2'])) { $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']); } if (isset($input['popular_question_3'])) { $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']); } if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) { $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']); } // Sanitize Loops Mailing List if (isset($input['loops_mailing_list'])) { $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']); } // Sanitize Triggered Phrase Response if (isset($input['triggered_phrase_response'])) { $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']); } if (isset($input['email_capture_response'])) { $new_input['email_capture_response'] = sanitize_textarea_field($input['email_capture_response']); } // Sanitize Brave Search Settings if (isset($input['brave_api_key'])) { $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']); } if (isset($input['brave_image_count'])) { $image_count = intval($input['brave_image_count']); $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4; } if (isset($input['brave_safe_search'])) { $allowed = array('strict', 'off'); $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict'; } if (isset($input['brave_news_count'])) { $news_count = intval($input['brave_news_count']); $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3; } if (isset($input['brave_country'])) { $new_input['brave_country'] = sanitize_text_field($input['brave_country']); } if (isset($input['brave_language'])) { $new_input['brave_language'] = sanitize_text_field($input['brave_language']); } if (isset($input['chat_toolbar_toggle'])) { $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off'; } // Sanitize PDF upload button toggle if (isset($input['show_pdf_upload_button'])) { $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off'; } else { $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked } // Sanitize Word upload button toggle if (isset($input['show_word_upload_button'])) { $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off'; } else { $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked } if (isset($input['pdf_intent_trigger_text'])) { $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']); } if (isset($input['pdf_intent_success_text'])) { $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']); } if (isset($input['pdf_intent_error_text'])) { $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']); } if (isset($input['pdf_max_pages'])) { $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']); if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) { $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range } } if (isset($input['live_agent_webhook_url'])) { $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']); } if (isset($input['live_agent_secret_key'])) { $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']); } // Live Agent Integration if (isset($input['live_agent_bot_token'])) { $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']); } if (isset($input['live_agent_status'])) { $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off'; } if (isset($input['live_agent_away_message'])) { $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']); } if (isset($input['live_agent_notification_message'])) { $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']); } return $new_input; } // Method to append the chatbot to the body public function mxchat_append_chatbot_to_body() { $options = get_option('mxchat_options'); if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') { echo do_shortcode('[mxchat_chatbot floating="yes"]'); } } private function mxchat_fetch_loops_mailing_lists($api_key) { $url = 'https://app.loops.so/api/v1/lists'; $response = wp_remote_get($url, array( 'headers' => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ) )); if (is_wp_error($response)) { return array(); } $body = wp_remote_retrieve_body($response); $lists = json_decode($body, true); return isset($lists) && is_array($lists) ? $lists : array(); } function mxchat_calculate_cosine_similarity($vec1, $vec2) { if (empty($vec1) || empty($vec2)) { return 0.0; } $dot_product = 0.0; $norm_a = 0.0; $norm_b = 0.0; for ($i = 0; $i < count($vec1); $i++) { $dot_product += $vec1[$i] * $vec2[$i]; $norm_a += pow($vec1[$i], 2); $norm_b += pow($vec2[$i], 2); } if ($norm_a == 0.0 || $norm_b == 0.0) { return 0.0; } else { return $dot_product / (sqrt($norm_a) * sqrt($norm_b)); } } /** * Update your existing display_admin_notices function to show notices on all MXChat pages */ public function display_admin_notices() { // Check if we're on a MXChat admin page $screen = get_current_screen(); if (!$screen || strpos($screen->base, 'mxchat') === false) { return; } //error_log('MxChat admin_notices hook fired on screen: ' . $screen->base); // Check for error notices $error_notice = get_transient('mxchat_admin_notice_error'); if ($error_notice) { //error_log('Found error transient: ' . $error_notice); echo '

' . wp_kses_post($error_notice) . '

'; delete_transient('mxchat_admin_notice_error'); //error_log('Displayed and deleted error transient'); } else { //error_log('No error transient found'); } // Check for success notices $success_notice = get_transient('mxchat_admin_notice_success'); if ($success_notice) { //error_log('Found success transient: ' . $success_notice); echo '

' . wp_kses_post($success_notice) . '

'; delete_transient('mxchat_admin_notice_success'); //error_log('Displayed and deleted success transient'); } // Check for info notices $info_notice = get_transient('mxchat_admin_notice_info'); if ($info_notice) { //error_log('Found info transient: ' . $info_notice); echo '

' . wp_kses_post($info_notice) . '

'; delete_transient('mxchat_admin_notice_info'); //error_log('Displayed and deleted info transient'); } // Display active processing status $this->display_processing_status(); } /** * Display current processing status */ private function display_processing_status() { $pdf_url = get_transient('mxchat_last_pdf_url'); $sitemap_url = get_transient('mxchat_last_sitemap_url'); if (!$pdf_url && !$sitemap_url) { return; } $pdf_status = $pdf_url ? $this->get_pdf_processing_status($pdf_url) : false; $sitemap_status = $sitemap_url ? $this->get_sitemap_processing_status($sitemap_url) : false; if ($sitemap_status && isset($sitemap_status['error']) && !empty($sitemap_status['error'])) { echo '
'; echo '

' . esc_html__('Sitemap Processing Error:', 'mxchat') . ' ' . esc_html($sitemap_status['error']) . '

'; echo '
'; } if ($pdf_status && isset($pdf_status['error']) && !empty($pdf_status['error'])) { echo '
'; echo '

' . esc_html__('PDF Processing Error:', 'mxchat') . ' ' . esc_html($pdf_status['error']) . '

'; echo '
'; } } } ?>