version = $version; } // ----------------------------------------------------------------------- // Asset enqueuing // ----------------------------------------------------------------------- /** * Enqueue admin stylesheets (only on this plugin's pages). * * @param string $hook_suffix Current admin page hook. */ public function enqueue_styles( $hook_suffix ) { if ( ! $this->is_plugin_page( $hook_suffix ) ) { return; } // No Bootstrap CSS – all styles are custom-scoped to avoid WP admin conflicts. wp_enqueue_style( 'ai-chatbot-admin', AI_CHATBOT_URL . 'admin/css/ai-chatbot-admin.css', array(), $this->version ); } /** * Enqueue admin scripts (only on this plugin's pages). * * @param string $hook_suffix Current admin page hook. */ public function enqueue_scripts( $hook_suffix ) { if ( ! $this->is_plugin_page( $hook_suffix ) ) { return; } // Bootstrap JS is no longer needed – tabs and modals use custom JS. wp_enqueue_script( 'ai-chatbot-admin', AI_CHATBOT_URL . 'admin/js/ai-chatbot-admin.js', array( 'jquery', 'wp-i18n' ), $this->version, true ); // Deliver JS translations from the languages/ folder. wp_set_script_translations( 'ai-chatbot-admin', AI_CHATBOT_DOMAIN, AI_CHATBOT_PATH . 'languages' ); wp_enqueue_script( 'chartjs', 'https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js', array(), '4.4.3', true ); wp_localize_script( 'ai-chatbot-admin', 'aiChatbotAdmin', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'ai_chatbot_nonce' ), 'pluginUrl' => AI_CHATBOT_URL, 'siteUrl' => get_site_url(), ) ); } /** * Check whether the current admin page belongs to this plugin. * * @param string $hook_suffix * @return bool */ private function is_plugin_page( $hook_suffix ) { $plugin_pages = array( 'toplevel_page_ai-chatbot', 'ai-chatbot_page_ai-chatbot-stats', 'admin_page_ai-chatbot-agent', // null-parent hidden page hook suffix ); return in_array( $hook_suffix, $plugin_pages, true ); } // ----------------------------------------------------------------------- // Menu registration // ----------------------------------------------------------------------- /** * Register the top-level admin menu item. */ public function register_menu() { add_menu_page( esc_html( PLUGIN_NAME ), esc_html( PLUGIN_NAME ), 'manage_options', 'ai-chatbot', array( $this, 'render_main_page' ), 'dashicons-format-chat', 80 ); // Hidden page: Detailed Agent Management View (Phase 2). // Using null parent is the WordPress-recommended way to register a page // that is routable via ?page= but never shown in the sidebar menu. // This avoids the remove_submenu_page() pattern which breaks get_admin_page_parent() // and causes "Sorry, you are not allowed to access this page." errors. add_submenu_page( null, // no parent → hidden from all menus esc_html__( 'Manage Agent', AI_CHATBOT_DOMAIN ), esc_html__( 'Manage Agent', AI_CHATBOT_DOMAIN ), 'manage_options', 'ai-chatbot-agent', array( $this, 'render_agent_page' ) ); } // ----------------------------------------------------------------------- // State machine helpers // ----------------------------------------------------------------------- /** * Determine the current plugin state. * * State 1 – Not authenticated (no cookie saved). * State 2 – Authenticated but no project ID yet. * State 3 – Authenticated and project ID saved. * * @return int 1, 2, or 3. */ private function get_state() { if ( ! Ai_Chatbot_API::get_saved_cookie() ) { return 1; } if ( ! get_option( Ai_Chatbot_API::opt_project_id() ) ) { return 2; } return 3; } /** * Ensure the project is provisioned (State 2 handler). * * Calls GET /v1/projects; if empty calls POST /v1/projects. * Saves the resulting project_id to the database. * * @return string|WP_Error Project ID on success. */ private function ensure_project() { $projects = Ai_Chatbot_API::get_projects(); if ( is_wp_error( $projects ) ) { return $projects; } if ( ! empty( $projects ) && isset( $projects[0]['project_id'] ) ) { $project_id = sanitize_text_field( $projects[0]['project_id'] ); update_option( Ai_Chatbot_API::opt_project_id(), $project_id ); return $project_id; } // No projects – auto-create one. $result = Ai_Chatbot_API::create_project(); if ( is_wp_error( $result ) ) { return $result; } return get_option( Ai_Chatbot_API::opt_project_id() ); } // ----------------------------------------------------------------------- // Page renderers // ----------------------------------------------------------------------- /** * Render the main admin page (state machine entry point). */ public function render_main_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', AI_CHATBOT_DOMAIN ) ); } $state = $this->get_state(); if ( 1 === $state ) { require_once AI_CHATBOT_PATH . 'admin/partials/ai-chatbot-admin-auth.php'; return; } // State 2: ensure project is provisioned before showing the dashboard. if ( 2 === $state ) { $project_id = $this->ensure_project(); if ( is_wp_error( $project_id ) ) { echo '

' . esc_html( $project_id->get_error_message() ) . '

'; return; } } // State 3: show agents. $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $agents = Ai_Chatbot_API::get_agents( $project_id ); if ( is_wp_error( $agents ) ) { // Likely session expired and refresh failed – bounce to login. if ( 'auth_expired' === $agents->get_error_code() ) { Ai_Chatbot_API::clear_auth(); require_once AI_CHATBOT_PATH . 'admin/partials/ai-chatbot-admin-auth.php'; return; } echo '

' . esc_html( $agents->get_error_message() ) . '

'; return; } require_once AI_CHATBOT_PATH . 'admin/partials/ai-chatbot-admin-dashboard.php'; } /** * Render the Detailed Agent Management page (Phase 2). */ public function render_agent_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', AI_CHATBOT_DOMAIN ) ); } if ( ! Ai_Chatbot_API::get_saved_cookie() ) { wp_safe_redirect( admin_url( 'admin.php?page=ai-chatbot' ) ); exit; } $chatbot_id = isset( $_GET['chatbot_id'] ) ? sanitize_text_field( wp_unslash( $_GET['chatbot_id'] ) ) : ''; $chatbot_name = isset( $_GET['chatbot_name'] ) ? sanitize_text_field( wp_unslash( $_GET['chatbot_name'] ) ) : __( 'Agent', AI_CHATBOT_DOMAIN ); $active_tab = isset( $_GET['active_tab'] ) ? sanitize_text_field( wp_unslash( $_GET['active_tab'] ) ) : 'aic-agent-panel-stats'; $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); if ( empty( $chatbot_id ) ) { wp_safe_redirect( admin_url( 'admin.php?page=ai-chatbot' ) ); exit; } require_once AI_CHATBOT_PATH . 'admin/partials/ai-chatbot-admin-agent-view.php'; } /** * Render the Stats sub-page. */ public function render_stats_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions.', AI_CHATBOT_DOMAIN ) ); } $chatbot_id = isset( $_GET['chatbot_id'] ) ? sanitize_text_field( wp_unslash( $_GET['chatbot_id'] ) ) : ''; $chatbot_name = isset( $_GET['chatbot_name'] ) ? sanitize_text_field( wp_unslash( $_GET['chatbot_name'] ) ) : ''; if ( empty( $chatbot_id ) ) { wp_safe_redirect( admin_url( 'admin.php?page=ai-chatbot' ) ); exit; } require_once AI_CHATBOT_PATH . 'admin/partials/ai-chatbot-admin-stats.php'; } // ----------------------------------------------------------------------- // AJAX handlers – Authentication // ----------------------------------------------------------------------- /** * Helper: verify nonce and return capability. */ private function verify_nonce() { check_ajax_referer( 'ai_chatbot_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', AI_CHATBOT_DOMAIN ) ), 403 ); } } /** * AJAX: sign up. */ public function ajax_signup() { $this->verify_nonce(); $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); $password = sanitize_text_field( wp_unslash( $_POST['password'] ?? '' ) ); $first_name = sanitize_text_field( wp_unslash( $_POST['first_name'] ?? '' ) ); $last_name = sanitize_text_field( wp_unslash( $_POST['last_name'] ?? '' ) ); $org_name = sanitize_text_field( wp_unslash( $_POST['org_name'] ?? '' ) ); if ( ! is_email( $email ) || empty( $password ) || empty( $first_name ) || empty( $org_name ) ) { wp_send_json_error( array( 'message' => __( 'Please fill in all required fields.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::signup( $email, $password, $first_name, $last_name, $org_name ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Account created successfully!', AI_CHATBOT_DOMAIN ) ) ); } /** * AJAX: login with email + password. */ public function ajax_login() { $this->verify_nonce(); $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); $password = sanitize_text_field( wp_unslash( $_POST['password'] ?? '' ) ); if ( ! is_email( $email ) || empty( $password ) ) { wp_send_json_error( array( 'message' => __( 'Email and password are required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::login( $email, $password ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Logged in successfully!', AI_CHATBOT_DOMAIN ) ) ); } /** * AJAX: initiate OTP login. */ public function ajax_otp_init() { $this->verify_nonce(); $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); if ( ! is_email( $email ) ) { wp_send_json_error( array( 'message' => __( 'A valid email address is required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::login_otp_init( $email ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } $session = isset( $result['session'] ) ? $result['session'] : ( is_string( $result ) ? $result : '' ); wp_send_json_success( array( 'message' => __( 'OTP sent to your email.', AI_CHATBOT_DOMAIN ), 'session' => $session, ) ); } /** * AJAX: verify OTP. */ public function ajax_otp_verify() { $this->verify_nonce(); $email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); $otp = sanitize_text_field( wp_unslash( $_POST['otp'] ?? '' ) ); $session = sanitize_text_field( wp_unslash( $_POST['session'] ?? '' ) ); if ( ! is_email( $email ) || empty( $otp ) || empty( $session ) ) { wp_send_json_error( array( 'message' => __( 'Email, OTP, and session are required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::verify_otp( $email, $otp, $session ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Logged in successfully!', AI_CHATBOT_DOMAIN ) ) ); } /** * AJAX: logout. */ public function ajax_logout() { $this->verify_nonce(); Ai_Chatbot_API::clear_auth(); wp_send_json_success( array( 'message' => __( 'Logged out.', AI_CHATBOT_DOMAIN ) ) ); } // ----------------------------------------------------------------------- // AJAX handlers – Agents // ----------------------------------------------------------------------- /** * AJAX: create agent, then immediately crawl the website. * Both operations run server-side in one request to avoid any * browser-abort race conditions from JS page reloads. */ public function ajax_create_agent() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) ); $language = sanitize_text_field( wp_unslash( $_POST['language'] ?? 'en' ) ); $website_url = esc_url_raw( wp_unslash( $_POST['website_url'] ?? '' ) ); if ( empty( $name ) ) { wp_send_json_error( array( 'message' => __( 'Agent name is required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::create_agent( $project_id, $name, $language, '' ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } // The create_agent API returns the chatbot ID as a plain string directly // in the `data` field: { "data": "chatbot_id_value" }. // After request() unwraps `data`, $result is that string. // Handle both that confirmed case and any future array-based response. if ( is_string( $result ) && '' !== $result ) { $chatbot_id = $result; } elseif ( is_array( $result ) ) { $chatbot_id = ''; foreach ( array( 'id', 'chatbotId', 'chatbot_id' ) as $key ) { if ( ! empty( $result[ $key ] ) ) { $chatbot_id = (string) $result[ $key ]; break; } } } else { $chatbot_id = ''; } // Crawl the website after agent creation (single attempt). $crawl_message = ''; if ( ! empty( $chatbot_id ) && ! empty( $website_url ) ) { $crawl = Ai_Chatbot_API::crawl_website( $project_id, $chatbot_id, $website_url ); if ( ! is_wp_error( $crawl ) ) { /* translators: %s: website URL */ $crawl_message = sprintf( __( ' Website crawl started for %s.', AI_CHATBOT_DOMAIN ), $website_url ); } } wp_send_json_success( array( 'message' => __( 'Agent created successfully!', AI_CHATBOT_DOMAIN ) . $crawl_message, 'chatbot_id' => $chatbot_id, ) ); } /** * AJAX: update agent. */ public function ajax_update_agent() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) ); $description = sanitize_textarea_field( wp_unslash( $_POST['description'] ?? 'You are a helpful AI Agent' ) ); $avatar_url = esc_url_raw( wp_unslash( $_POST['avatar_url'] ?? '' ) ); if ( empty( $chatbot_id ) || empty( $name ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID and name are required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::update_agent( $project_id, $chatbot_id, $name, $description, $avatar_url ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Agent updated successfully!', AI_CHATBOT_DOMAIN ) ) ); } /** * AJAX: crawl website for a newly created agent. */ public function ajax_crawl_website() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $url = esc_url_raw( wp_unslash( $_POST['website_url'] ?? '' ) ); if ( empty( $chatbot_id ) || empty( $url ) ) { wp_send_json_error( array( 'message' => __( 'Chatbot ID and website URL are required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::crawl_website( $project_id, $chatbot_id, $url ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => sprintf( /* translators: %s: website URL */ __( 'Website crawl started for %s', AI_CHATBOT_DOMAIN ), $url ), ) ); } /** * AJAX: delete agent. */ public function ajax_delete_agent() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID is required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::delete_agent( $project_id, $chatbot_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Agent deleted.', AI_CHATBOT_DOMAIN ) ) ); } /** * AJAX: get agent stats. */ public function ajax_get_stats() { $this->verify_nonce(); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $start = sanitize_text_field( wp_unslash( $_POST['start_datetime'] ?? '' ) ); $end = sanitize_text_field( wp_unslash( $_POST['end_datetime'] ?? '' ) ); $time_offset = (int) ( $_POST['time_offset'] ?? 0 ); if ( empty( $chatbot_id ) || empty( $start ) || empty( $end ) ) { wp_send_json_error( array( 'message' => __( 'chatbot_id, start_datetime, and end_datetime are required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_agent_stats( $chatbot_id, $start, $end, $time_offset ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } // ----------------------------------------------------------------------- // AJAX handlers – Phase 2 Agent Management // ----------------------------------------------------------------------- /** AJAX: get appearance settings. */ public function ajax_get_appearance() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_appearance_settings( $project_id, $chatbot_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } /** AJAX: update appearance settings. */ public function ajax_update_appearance() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } // Decode the full settings payload (sent as JSON string to preserve all fields). // Use wp_unslash only – sanitize_text_field would strip HTML-like content within // JSON string values and corrupt the payload before json_decode runs. $payload_raw = wp_unslash( $_POST['payload'] ?? '{}' ); $payload = json_decode( $payload_raw, true ); if ( ! is_array( $payload ) ) { wp_send_json_error( array( 'message' => __( 'Invalid payload.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::update_appearance_settings( $project_id, $chatbot_id, $payload ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Appearance settings saved!', AI_CHATBOT_DOMAIN ) ) ); } // ----------------------------------------------------------------------- // Widget Tabs (Tab Integrations) // ----------------------------------------------------------------------- /** AJAX: load widget integrations for an agent. */ public function ajax_get_widget_tabs() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_appearance_settings( $project_id, $chatbot_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } $integrations = isset( $result['widget_integrations'] ) && is_array( $result['widget_integrations'] ) ? $result['widget_integrations'] : array(); wp_send_json_success( $integrations ); } /** AJAX: save widget integrations for an agent (fetch-merge-put strategy). */ public function ajax_save_widget_tabs() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $raw = wp_unslash( $_POST['integrations'] ?? '[]' ); $integrations = json_decode( $raw, true ); if ( ! is_array( $integrations ) ) { wp_send_json_error( array( 'message' => __( 'Invalid integrations payload.', AI_CHATBOT_DOMAIN ) ) ); } if ( count( $integrations ) > 5 ) { wp_send_json_error( array( 'message' => __( 'Maximum 5 integrations allowed.', AI_CHATBOT_DOMAIN ) ) ); } $allowed_providers = array( 'calendly', 'cal', 'google-calendar', 'whatsapp', 'linkedin', 'twitter', 'tiktok', 'instagram', 'facebook', 'youtube', 'custom', 'other' ); $allowed_icons = array( 'calendar', 'phone', 'chat', 'globe', 'link', 'social', 'mail', 'star' ); $allowed_types = array( 'iframe', 'link', 'whatsapp', 'social' ); $allowed_methods = array( 'url', 'code' ); $allowed_platforms = array( 'linkedin', 'twitter', 'instagram', 'tiktok', 'facebook', 'youtube', 'other', '' ); $allowed_embed_html = array( 'iframe' => array( 'src' => true, 'width' => true, 'height' => true, 'frameborder' => true, 'allowfullscreen' => true, 'allow' => true, 'style' => true, 'class' => true, 'title' => true, 'referrerpolicy' => true, 'sandbox' => true, 'loading' => true ), 'script' => array( 'src' => true, 'type' => true, 'async' => true, 'defer' => true ), 'div' => array( 'id' => true, 'class' => true, 'style' => true ), ); // These fields are optional but enforce a 1-N character range when // provided, so an empty string fails validation while null does not // (same partial-update contract as the appearance settings endpoint). $null_if_blank = function( $value ) { return ( '' === trim( (string) $value ) ) ? null : $value; }; $sanitized = array(); foreach ( $integrations as $item ) { if ( ! is_array( $item ) ) { continue; } $type = sanitize_text_field( $item['integration_type'] ?? 'link' ); if ( ! in_array( $type, $allowed_types, true ) ) { $type = 'link'; } $provider = sanitize_text_field( $item['integration_provider'] ?? 'custom' ); if ( ! in_array( $provider, $allowed_providers, true ) ) { $provider = 'custom'; } $icon = sanitize_text_field( $item['tab_icon'] ?? 'globe' ); if ( ! in_array( $icon, $allowed_icons, true ) ) { $icon = 'globe'; } $method = sanitize_text_field( $item['embed_method'] ?? 'url' ); if ( ! in_array( $method, $allowed_methods, true ) ) { $method = 'url'; } $platform = sanitize_text_field( $item['social_platform'] ?? '' ); if ( ! in_array( $platform, $allowed_platforms, true ) ) { $platform = 'other'; } // Server requires 7-15 digits, digits only. $whatsapp_number = preg_replace( '/\D/', '', (string) ( $item['whatsapp_number'] ?? '' ) ); if ( strlen( $whatsapp_number ) < 7 || strlen( $whatsapp_number ) > 15 ) { $whatsapp_number = ''; } $sanitized[] = array( 'tab_name' => $null_if_blank( sanitize_text_field( $item['tab_name'] ?? '' ) ), 'tab_icon' => $icon, 'header_title' => $null_if_blank( sanitize_text_field( $item['header_title'] ?? '' ) ), 'header_sub_title' => $null_if_blank( sanitize_text_field( $item['header_sub_title'] ?? '' ) ), 'integration_type' => $type, 'is_enable' => ! empty( $item['is_enable'] ), 'integration_provider' => $provider, 'embed_method' => $method, 'url' => $null_if_blank( esc_url_raw( $item['url'] ?? '' ) ), 'embed_code' => $null_if_blank( wp_kses( $item['embed_code'] ?? '', $allowed_embed_html ) ), 'button_text' => $null_if_blank( sanitize_text_field( $item['button_text'] ?? '' ) ), 'open_in_new_tab' => ! empty( $item['open_in_new_tab'] ), 'whatsapp_number' => $null_if_blank( $whatsapp_number ), 'prefilled_message' => $null_if_blank( sanitize_textarea_field( $item['prefilled_message'] ?? '' ) ), 'social_platform' => $platform, ); } // Fetch-merge-PUT: preserve all other appearance fields. $current = Ai_Chatbot_API::get_appearance_settings( $project_id, $chatbot_id ); if ( is_wp_error( $current ) ) { wp_send_json_error( array( 'message' => $current->get_error_message() ) ); } if ( ! is_array( $current ) ) { $current = array(); } $current['widget_integrations'] = $sanitized; $result = Ai_Chatbot_API::update_appearance_settings( $project_id, $chatbot_id, $current ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Widget tabs saved successfully!', AI_CHATBOT_DOMAIN ) ) ); } // ----------------------------------------------------------------------- // WooCommerce Integration // ----------------------------------------------------------------------- /** * AJAX: get WooCommerce integration status for the current agent. * * Checks the agent's tool linkages (per-chatbot) rather than the project-level * credentials so the status is accurate per chatbot. */ public function ajax_get_woocommerce_status() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); if ( empty( $project_id ) ) { wp_send_json_error( array( 'message' => __( 'Project not found.', AI_CHATBOT_DOMAIN ) ) ); } $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $linkages = Ai_Chatbot_API::get_tool_linkages( $project_id, $chatbot_id ); if ( is_wp_error( $linkages ) ) { wp_send_json_error( array( 'message' => $linkages->get_error_message() ) ); } $wc_linkage = null; if ( is_array( $linkages ) ) { foreach ( $linkages as $link ) { if ( isset( $link['integration_id'] ) && 1 === (int) $link['integration_id'] ) { $wc_linkage = $link; break; } } } wp_send_json_success( array( 'enabled' => ! empty( $wc_linkage ), ) ); } /** * AJAX: enable or disable WooCommerce integration for the current agent. * * On every call (enable or disable) existing linkages and credentials for * integration_id = 1 are cleaned up first. If enabling, fresh WooCommerce * API credentials are generated, stored in the backend, and linked to the * agent. If anything fails during enable the rollback path cleans up. */ public function ajax_toggle_woocommerce() { $this->verify_nonce(); global $wpdb; $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); if ( empty( $project_id ) ) { wp_send_json_error( array( 'message' => __( 'Project not found.', AI_CHATBOT_DOMAIN ) ) ); } $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $enable = ( isset( $_POST['enable'] ) && 'true' === sanitize_text_field( wp_unslash( $_POST['enable'] ) ) ); // ── Step 1: Remove any existing WooCommerce linkage for this agent ────── $linkages = Ai_Chatbot_API::get_tool_linkages( $project_id, $chatbot_id ); if ( ! is_wp_error( $linkages ) && is_array( $linkages ) ) { foreach ( $linkages as $link ) { if ( isset( $link['integration_id'] ) && 1 === (int) $link['integration_id'] && isset( $link['linkage_id'] ) ) { Ai_Chatbot_API::unlink_tool_credential( $project_id, $chatbot_id, $link['linkage_id'] ); } } } // ── Step 2: Remove any existing WooCommerce credential for this project ─ $credentials = Ai_Chatbot_API::get_tool_credentials( $project_id ); if ( ! is_wp_error( $credentials ) && is_array( $credentials ) ) { foreach ( $credentials as $cred ) { if ( isset( $cred['integration_id'] ) && 1 === (int) $cred['integration_id'] && isset( $cred['id'] ) ) { Ai_Chatbot_API::delete_tool_credential( $project_id, $cred['id'] ); } } } // ── Step 3: Remove the local WooCommerce API key (if one was stored) ──── $existing_key_id = get_option( PLUGIN_SLUG . '_wc_key_id' ); if ( $existing_key_id ) { $wpdb->delete( $wpdb->prefix . 'woocommerce_api_keys', array( 'key_id' => (int) $existing_key_id ), array( '%d' ) ); } delete_option( PLUGIN_SLUG . '_wc_key_id' ); // If disabling, we are done. if ( ! $enable ) { wp_send_json_success( array( 'message' => __( 'WooCommerce integration disabled.', AI_CHATBOT_DOMAIN ), 'enabled' => false, ) ); } // ── Step 4: Verify WooCommerce is active before generating credentials ── if ( ! class_exists( 'WooCommerce' ) ) { wp_send_json_error( array( 'message' => __( 'WooCommerce is not active on this site.', AI_CHATBOT_DOMAIN ) ) ); } // ── Step 5: Generate fresh WooCommerce REST API credentials ───────────── $consumer_key = 'ck_' . wc_rand_hash(); $consumer_secret = 'cs_' . wc_rand_hash(); $store_url = get_site_url(); $inserted = $wpdb->insert( $wpdb->prefix . 'woocommerce_api_keys', array( 'user_id' => get_current_user_id(), 'description' => 'AI Chatbot WooCommerce Integration', 'permissions' => 'read', 'consumer_key' => wc_api_hash( $consumer_key ), 'consumer_secret' => $consumer_secret, 'truncated_key' => substr( $consumer_key, -7 ), ), array( '%d', '%s', '%s', '%s', '%s', '%s' ) ); if ( ! $inserted ) { wp_send_json_error( array( 'message' => __( 'Failed to generate WooCommerce API credentials.', AI_CHATBOT_DOMAIN ) ) ); } $key_id = $wpdb->insert_id; update_option( PLUGIN_SLUG . '_wc_key_id', $key_id ); // ── Step 6: Register the credentials with the AI backend ───────────────── $auth_data = array( 'store_url' => $store_url, 'consumer_key' => $consumer_key, 'consumer_secret' => $consumer_secret, ); $cred_result = Ai_Chatbot_API::create_tool_credential( $project_id, 1, 'basic', wp_json_encode( $auth_data ), 'woocommerce store credentials' ); if ( is_wp_error( $cred_result ) ) { // Rollback: remove the WooCommerce key we just created. $wpdb->delete( $wpdb->prefix . 'woocommerce_api_keys', array( 'key_id' => $key_id ), array( '%d' ) ); delete_option( PLUGIN_SLUG . '_wc_key_id' ); wp_send_json_error( array( 'message' => $cred_result->get_error_message() ) ); } // The POST response may return the credential ID under different field names // depending on the API version. Try the most common variants. $credential_id = null; foreach ( array( 'id', 'credential_id', '_id' ) as $_key ) { if ( ! empty( $cred_result[ $_key ] ) ) { $credential_id = $cred_result[ $_key ]; break; } } // If the create response didn't include the ID, re-fetch the credentials list // and locate the newly-created WooCommerce credential by integration_id. // This handles API versions that return a minimal or differently-shaped response. if ( empty( $credential_id ) ) { $fresh_creds = Ai_Chatbot_API::get_tool_credentials( $project_id ); if ( ! is_wp_error( $fresh_creds ) && is_array( $fresh_creds ) ) { foreach ( $fresh_creds as $c ) { if ( isset( $c['integration_id'] ) && 1 === (int) $c['integration_id'] ) { foreach ( array( 'id', 'credential_id', '_id' ) as $_key ) { if ( ! empty( $c[ $_key ] ) ) { $credential_id = $c[ $_key ]; break 2; } } } } } } // If we still can't resolve the credential ID after all attempts, clean up and bail. if ( empty( $credential_id ) ) { $wpdb->delete( $wpdb->prefix . 'woocommerce_api_keys', array( 'key_id' => $key_id ), array( '%d' ) ); delete_option( PLUGIN_SLUG . '_wc_key_id' ); wp_send_json_error( array( 'message' => __( 'Could not retrieve credential ID from API response.', AI_CHATBOT_DOMAIN ) ) ); } // ── Step 7: Link the credential to this agent ───────────────────────────── $link_result = Ai_Chatbot_API::link_tool_credential( $project_id, $chatbot_id, 1, $credential_id ); if ( is_wp_error( $link_result ) ) { // Rollback: delete credential and WooCommerce key. Ai_Chatbot_API::delete_tool_credential( $project_id, $credential_id ); $wpdb->delete( $wpdb->prefix . 'woocommerce_api_keys', array( 'key_id' => $key_id ), array( '%d' ) ); delete_option( PLUGIN_SLUG . '_wc_key_id' ); wp_send_json_error( array( 'message' => $link_result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'WooCommerce integration enabled successfully!', AI_CHATBOT_DOMAIN ), 'enabled' => true, ) ); } /** AJAX: get AI settings. */ public function ajax_get_ai_settings() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_ai_settings( $project_id, $chatbot_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } /** AJAX: update AI settings. */ public function ajax_update_ai_settings() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } // Use wp_unslash only - sanitize_text_field would strip HTML-like // content and collapse newlines within multi-line values (e.g. // system_prompt_text) before json_decode runs, corrupting the payload. $payload_raw = wp_unslash( $_POST['payload'] ?? '{}' ); $payload = json_decode( $payload_raw, true ); if ( ! is_array( $payload ) ) { wp_send_json_error( array( 'message' => __( 'Invalid payload.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::update_ai_settings( $project_id, $chatbot_id, $payload ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'AI settings saved!', AI_CHATBOT_DOMAIN ) ) ); } /** AJAX: get knowledge-base content (paginated). */ public function ajax_get_kb() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $page_number = max( 1, (int) ( $_POST['page_number'] ?? 1 ) ); $page_size = max( 1, min( 50, (int) ( $_POST['page_size'] ?? 10 ) ) ); $asset_type = sanitize_text_field( wp_unslash( $_POST['assetType'] ?? 'WebsiteURL' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_linked_content( $project_id, $chatbot_id, $page_number, $page_size, $asset_type ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } /** AJAX: link content to agent (add to knowledge base). */ public function ajax_link_content() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $content = sanitize_textarea_field( wp_unslash( $_POST['content'] ?? '' ) ); $file_name = sanitize_text_field( wp_unslash( $_POST['file_name'] ?? '' ) ); if ( empty( $chatbot_id ) || empty( $content ) || empty( $file_name ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID, content, and file name are required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::link_content( $project_id, $chatbot_id, $content, $file_name ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Content added to knowledge base!', AI_CHATBOT_DOMAIN ), 'item' => $result ) ); } /** AJAX: delink content from agent (remove from knowledge base). */ public function ajax_delink_content() { $this->verify_nonce(); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $linkage_id = (int) ( $_POST['linkage_id'] ?? 0 ); $asset_id = (int) ( $_POST['asset_id'] ?? 0 ); $asset_type = sanitize_text_field( wp_unslash( $_POST['asset_type'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::delink_content( $linkage_id, $chatbot_id, $asset_id, $asset_type ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } $delinked = ( true === $result ) || ( isset( $result['is_delinked'] ) && $result['is_delinked'] ); if ( ! $delinked ) { wp_send_json_error( array( 'message' => __( 'Failed to remove content.', AI_CHATBOT_DOMAIN ) ) ); } wp_send_json_success( array( 'message' => __( 'Content removed from knowledge base.', AI_CHATBOT_DOMAIN ) ) ); } // ----------------------------------------------------------------------- // AJAX handler – Link a WordPress post/page/product to the knowledge base // ----------------------------------------------------------------------- /** * Extract all public content from a WordPress post and send it to the * Robofy knowledge-base API. * * POST params: chatbot_id, post_id */ public function ajax_link_wp_post() { $this->verify_nonce(); $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $post_id = (int) ( $_POST['post_id'] ?? 0 ); if ( empty( $chatbot_id ) || $post_id <= 0 ) { wp_send_json_error( array( 'message' => __( 'Agent ID and post ID are required.', AI_CHATBOT_DOMAIN ) ) ); } $post = get_post( $post_id ); if ( ! $post || 'publish' !== $post->post_status ) { wp_send_json_error( array( 'message' => __( 'Post not found or not published.', AI_CHATBOT_DOMAIN ) ) ); } $content = $this->build_post_content( $post ); if ( empty( $content ) ) { wp_send_json_error( array( 'message' => __( 'No extractable content found for this post.', AI_CHATBOT_DOMAIN ) ) ); } $file_name = get_permalink( $post_id ); if ( empty( $file_name ) ) { $file_name = get_the_title( $post_id ); if ( empty( $file_name ) ) { $file_name = (string) $post_id; } } $result = Ai_Chatbot_API::link_content( $project_id, $chatbot_id, $content, $file_name ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } // Persist the linkage so the auto-refresh hook can update it on future saves. $existing = get_post_meta( $post_id, '_ai_chatbot_kb_linkage', true ); if ( ! is_array( $existing ) ) { $existing = array(); } // If this chatbot was linked before, replace its old entry (handles re-linking). $existing = array_values( array_filter( $existing, function ( $e ) use ( $chatbot_id ) { return isset( $e['chatbot_id'] ) && $e['chatbot_id'] !== $chatbot_id; } ) ); $existing[] = array( 'chatbot_id' => $chatbot_id, 'linkage_id' => isset( $result['linkage_id'] ) ? (int) $result['linkage_id'] : 0, 'asset_id' => isset( $result['asset_id'] ) ? (int) $result['asset_id'] : 0, ); update_post_meta( $post_id, '_ai_chatbot_kb_linkage', $existing ); wp_send_json_success( array( 'message' => __( 'Content added to knowledge base.', AI_CHATBOT_DOMAIN ), 'item' => $result, ) ); } // ----------------------------------------------------------------------- // Content extraction helper // ----------------------------------------------------------------------- /** * Build a plain-text content string from a WordPress post or product. * Used by ajax_link_wp_post() and on_post_saved(). * * @param WP_Post $post * @return string Empty string when nothing could be extracted. */ private function build_post_content( WP_Post $post ): string { $post_id = $post->ID; $parts = array(); // ── Title ────────────────────────────────────────────────────────── $title = get_the_title( $post ); if ( $title ) { $parts[] = 'Title: ' . $title; } // ── Meta description (Yoast → RankMath → AIOSEO) ────────────────── $meta_desc = get_post_meta( $post_id, '_yoast_wpseo_metadesc', true ); if ( empty( $meta_desc ) ) { $meta_desc = get_post_meta( $post_id, 'rank_math_description', true ); } if ( empty( $meta_desc ) ) { $meta_desc = get_post_meta( $post_id, '_aioseop_description', true ); } if ( ! empty( $meta_desc ) ) { $parts[] = 'Meta Description: ' . $meta_desc; } // ── SEO focus keyword (Yoast → RankMath) ────────────────────────── $seo_keywords = get_post_meta( $post_id, '_yoast_wpseo_focuskw', true ); if ( empty( $seo_keywords ) ) { $seo_keywords = get_post_meta( $post_id, 'rank_math_focus_keyword', true ); } if ( ! empty( $seo_keywords ) ) { $parts[] = 'SEO Keywords: ' . $seo_keywords; } // ── Taxonomy: categories & tags ──────────────────────────────────── if ( in_array( $post->post_type, array( 'post', 'page' ), true ) ) { $categories = wp_get_post_categories( $post_id, array( 'fields' => 'names' ) ); if ( ! empty( $categories ) && ! is_wp_error( $categories ) ) { $parts[] = 'Categories: ' . implode( ', ', $categories ); } $tags = wp_get_post_tags( $post_id, array( 'fields' => 'names' ) ); if ( ! empty( $tags ) && ! is_wp_error( $tags ) ) { $parts[] = 'Tags: ' . implode( ', ', $tags ); } } // ── WooCommerce product-specific fields ──────────────────────────── if ( 'product' === $post->post_type && function_exists( 'wc_get_product' ) ) { $product = wc_get_product( $post_id ); if ( $product ) { $price = wp_strip_all_tags( $product->get_price_html() ); if ( $price ) { $parts[] = 'Price: ' . $price; } $short_desc = wp_strip_all_tags( $product->get_short_description() ); if ( $short_desc ) { $parts[] = 'Short Description: ' . $short_desc; } $sku = $product->get_sku(); if ( $sku ) { $parts[] = 'SKU: ' . $sku; } $prod_cats = wc_get_product_category_list( $post_id, ', ' ); if ( $prod_cats ) { $parts[] = 'Product Categories: ' . wp_strip_all_tags( $prod_cats ); } $prod_tags = wc_get_product_tag_list( $post_id, ', ' ); if ( $prod_tags ) { $parts[] = 'Product Tags: ' . wp_strip_all_tags( $prod_tags ); } } } // ── Body content ─────────────────────────────────────────────────── $body = wp_strip_all_tags( apply_filters( 'the_content', $post->post_content ) ); $body = html_entity_decode( $body, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); $body = preg_replace( '/\s{3,}/', "\n\n", trim( $body ) ); if ( ! empty( $body ) ) { $parts[] = 'Content: ' . $body; } return implode( "\n\n", $parts ); } // ----------------------------------------------------------------------- // save_post hook – auto-refresh KB when a linked post/product is updated // ----------------------------------------------------------------------- /** * Auto-refresh chatbot knowledge base when a linked post/product is saved. * Hooked to save_post at priority 99 (after WooCommerce finishes at 34). * * @param int $post_id Post ID. * @param WP_Post $post Post object after the save. * @param bool $update True on update, false on first insert. */ public function on_post_saved( $post_id, $post, $update ) { // Skip revisions and auto-saves — only process real user-initiated saves. if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } // Only refresh when the post is published. if ( 'publish' !== $post->post_status ) { return; } // Deduplicate: WordPress (and WooCommerce) can fire save_post several // times for the same post ID in a single request. static $processed = array(); if ( isset( $processed[ $post_id ] ) ) { return; } $processed[ $post_id ] = true; // Only act on posts that were previously linked to a chatbot. $linkages = get_post_meta( $post_id, '_ai_chatbot_kb_linkage', true ); if ( empty( $linkages ) || ! is_array( $linkages ) ) { return; } $project_id = get_option( Ai_Chatbot_API::opt_project_id() ); if ( empty( $project_id ) ) { return; } // Build fresh content. Wrap in try/catch so a faulty third-party // content filter can never abort the post save operation. try { $content = $this->build_post_content( $post ); } catch ( \Exception $e ) { return; } catch ( \Throwable $e ) { return; } if ( empty( $content ) ) { return; } $file_name = get_permalink( $post_id ); if ( empty( $file_name ) ) { $file_name = get_the_title( $post_id ); if ( empty( $file_name ) ) { $file_name = (string) $post_id; } } $updated_linkages = array(); foreach ( $linkages as $linkage ) { // Skip malformed entries. if ( ! isset( $linkage['chatbot_id'], $linkage['linkage_id'], $linkage['asset_id'] ) ) { continue; } $chatbot_id = $linkage['chatbot_id']; if ( empty( $chatbot_id ) ) { continue; } // Remove the stale KB entry. Errors are silently ignored — a stale // entry is harmless and must not block the fresh upload. if ( ! empty( $linkage['linkage_id'] ) || ! empty( $linkage['asset_id'] ) ) { Ai_Chatbot_API::delink_content( (int) $linkage['linkage_id'], $chatbot_id, (int) $linkage['asset_id'] ); } // Upload fresh content. $result = Ai_Chatbot_API::link_content( $project_id, $chatbot_id, $content, $file_name ); if ( ! is_wp_error( $result ) && isset( $result['linkage_id'] ) ) { $updated_linkages[] = array( 'chatbot_id' => $chatbot_id, 'linkage_id' => (int) $result['linkage_id'], 'asset_id' => isset( $result['asset_id'] ) ? (int) $result['asset_id'] : 0, ); } else { // Keep the old entry so the next save can retry. $updated_linkages[] = $linkage; } } if ( ! empty( $updated_linkages ) ) { update_post_meta( $post_id, '_ai_chatbot_kb_linkage', $updated_linkages ); } } // ----------------------------------------------------------------------- // AJAX handler – WordPress site content (pages / posts / products) // ----------------------------------------------------------------------- /** * Return paginated WordPress content for the Knowledge Base content picker. * * POST params: post_type (page|post|product), page, per_page, search */ public function ajax_get_site_content() { $this->verify_nonce(); $allowed_types = array( 'page', 'post' ); if ( class_exists( 'WooCommerce' ) ) { $allowed_types[] = 'product'; } $post_type = sanitize_key( wp_unslash( $_POST['post_type'] ?? 'page' ) ); if ( ! in_array( $post_type, $allowed_types, true ) ) { $post_type = 'page'; } $page = max( 1, (int) ( $_POST['page'] ?? 1 ) ); $per_page = max( 1, min( 50, (int) ( $_POST['per_page'] ?? 20 ) ) ); $search = sanitize_text_field( wp_unslash( $_POST['search'] ?? '' ) ); $args = array( 'post_type' => $post_type, 'post_status' => 'publish', 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'modified', 'order' => 'DESC', 'no_found_rows' => false, ); if ( ! empty( $search ) ) { $args['s'] = $search; } $query = new WP_Query( $args ); $items = array(); foreach ( $query->posts as $post ) { $items[] = array( 'id' => $post->ID, 'title' => $post->post_title ?: __( '(no title)', AI_CHATBOT_DOMAIN ), 'type' => $post->post_type, 'modified' => $post->post_modified, 'url' => get_permalink( $post->ID ), ); } wp_reset_postdata(); wp_send_json_success( array( 'items' => $items, 'total' => (int) $query->found_posts, 'pages' => (int) $query->max_num_pages, 'page' => $page, 'per_page' => $per_page, ) ); } // ----------------------------------------------------------------------- // AJAX handler – Frontend embed toggle // ----------------------------------------------------------------------- /** * Enable or disable this agent as the active frontend embed. * * POST params: chatbot_id, project_id, enable (1|0) */ public function ajax_toggle_frontend_embed() { $this->verify_nonce(); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $project_id = sanitize_text_field( wp_unslash( $_POST['project_id'] ?? '' ) ); $enable = ! empty( $_POST['enable'] ) && '1' === (string) $_POST['enable']; if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $opt_key = PLUGIN_SLUG . '_frontend_embed'; if ( $enable ) { update_option( $opt_key, array( 'chatbot_id' => $chatbot_id, 'project_id' => $project_id, ) ); wp_send_json_success( array( 'enabled' => true, 'message' => __( 'Chatbot enabled on your website.', AI_CHATBOT_DOMAIN ), ) ); } else { // Only disable if this agent is the currently active one. $current = get_option( $opt_key ); if ( ! empty( $current['chatbot_id'] ) && $current['chatbot_id'] === $chatbot_id ) { delete_option( $opt_key ); } wp_send_json_success( array( 'enabled' => false, 'message' => __( 'Chatbot disabled on your website.', AI_CHATBOT_DOMAIN ), ) ); } } /** AJAX: get inbox sessions */ public function ajax_get_inbox_sessions() { $this->verify_nonce(); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $next_page_token = sanitize_text_field( wp_unslash( $_POST['next_page_token'] ?? '' ) ); if ( empty( $chatbot_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_inbox_sessions( $chatbot_id, $next_page_token ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } /** AJAX: get inbox chats */ public function ajax_get_inbox_chats() { $this->verify_nonce(); $chatbot_id = sanitize_text_field( wp_unslash( $_POST['chatbot_id'] ?? '' ) ); $session_id = sanitize_text_field( wp_unslash( $_POST['session_id'] ?? '' ) ); if ( empty( $chatbot_id ) || empty( $session_id ) ) { wp_send_json_error( array( 'message' => __( 'Agent ID and Session ID required.', AI_CHATBOT_DOMAIN ) ) ); } $result = Ai_Chatbot_API::get_inbox_chats( $chatbot_id, $session_id ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } /** AJAX: get account limits */ public function ajax_get_account_limits() { $this->verify_nonce(); $result = Ai_Chatbot_API::get_account_limits(); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( $result ); } }