verify_nonce_or_fail(); $session_id = Agentorie_API_Helpers::get_input( 'session_id', '', 'key' ); $message = Agentorie_API_Helpers::get_input( 'message', '', 'textarea' ); if ( empty( $session_id ) || empty( $message ) ) { Agentorie_API_Helpers::send_error( __( 'Missing required data.', 'agentorie-chat' ) ); } if ( ! Agentorie_Session::validate_session_id( $session_id ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid session.', 'agentorie-chat' ) ); } $message = Agentorie_API_Helpers::sanitize_message( $message ); if ( empty( $message ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid message.', 'agentorie-chat' ) ); } if ( ! Agentorie_API_Helpers::check_message_rate_limit( $session_id ) ) { Agentorie_API_Helpers::send_error( __( 'Too many messages. Please wait.', 'agentorie-chat' ), 429 ); } // Per-IP guard on the AI path so rotating session IDs cannot flood the backend. if ( ! Agentorie_API_Helpers::check_ip_rate_limit( 'chat', Agentorie_API_Helpers::RATE_LIMIT_IP_CHAT ) ) { Agentorie_API_Helpers::send_error( __( 'Too many messages. Please wait.', 'agentorie-chat' ), 429 ); } $honeypot = Agentorie_API_Helpers::get_input( 'website', '', 'text' ); if ( ! empty( $honeypot ) ) { Agentorie_API_Helpers::send_success( array( 'message' => __( 'Thank you for your message.', 'agentorie-chat' ) ) ); } $context = array( 'visitor_id' => Agentorie_API_Helpers::get_input( 'visitor_id', '', 'text' ), 'current_url' => Agentorie_API_Helpers::get_input( 'current_url', '', 'url' ), 'page_title' => Agentorie_API_Helpers::get_input( 'page_title', '', 'text' ), 'referrer' => esc_url_raw( wp_get_referer() ? wp_get_referer() : '' ), 'user_agent' => Agentorie_Session::get_user_agent(), ); // Check admin_takeover directly from database (source of truth). // This ensures we don't miss takeover even if transient is stale. $db_session = Agentorie_Database::get_session( $session_id ); $is_admin_takeover = $db_session && ! empty( $db_session['admin_takeover'] ); $session = Agentorie_Session::get_or_create( $session_id ); $session->update_visitor_info( $context['visitor_id'], $context['current_url'], $context['page_title'] ); // Sync takeover status from DB to session object. if ( $is_admin_takeover && ! $session->is_admin_takeover() ) { $admin_id = isset( $db_session['admin_user_id'] ) ? $db_session['admin_user_id'] : null; $session->set_admin_takeover( true, $admin_id ); $session->save(); } $user_message_id = Agentorie_Database::insert_message( array( 'session_id' => $session_id, 'role' => 'user', 'message' => $message, ) ); // If admin has taken over, don't call AI - just save message and return. if ( $is_admin_takeover ) { $session->increment_messages(); $session->set_last_message( $message ); $session->save(); $session->sync_to_database(); Agentorie_API_Helpers::send_success( array( 'message' => null, 'admin_takeover' => true, 'action' => 'wait_admin', 'message_id' => $user_message_id, ) ); return; // Explicit return for safety. } $exclude_from_stats = Agentorie_API_Helpers::is_excluded_ip(); $saas = Agentorie_API_SaaS::get_instance(); $response = $saas->send_message( $session_id, $message, $context, $exclude_from_stats ); if ( is_wp_error( $response ) ) { Agentorie_API_Helpers::send_error( __( 'Sorry, I am experiencing a technical issue. Please try again.', 'agentorie-chat' ) ); } $ai_message = isset( $response['response'] ) ? $response['response'] : ( isset( $response['message'] ) ? $response['message'] : '' ); $flags = isset( $response['flags'] ) ? $response['flags'] : array(); if ( ! empty( $flags['negotiate_detected'] ) || false !== strpos( $ai_message, '[NEGOTIATE_DETECTED]' ) ) { $ai_message = str_replace( '[NEGOTIATE_DETECTED]', '', $ai_message ); $ai_message = trim( $ai_message ); if ( ! empty( $flags['negotiate_detected'] ) ) { $session->set_negotiate_detected( true ); } } if ( false !== strpos( $ai_message, '[HUMAN_REQUESTED]' ) ) { $ai_message = str_replace( '[HUMAN_REQUESTED]', '', $ai_message ); $ai_message = trim( $ai_message ); } if ( ! empty( $flags['human_requested'] ) ) { $session->set_human_requested( true ); } if ( preg_match( '/\[LEAD_COLLECTED:(\{.*?\})\]/s', $ai_message, $lead_matches ) ) { $ai_message = preg_replace( '/\[LEAD_COLLECTED:\{.*?\}\]/s', '', $ai_message ); $ai_message = trim( $ai_message ); $lead_data = json_decode( $lead_matches[1], true ); if ( $lead_data && is_array( $lead_data ) ) { $this->process_lead_collection( $lead_data, $session_id, $context ); } } // Process lead collection from flags (new format from SaaS). // lead_data format: ['name' => '...', 'email' => '...', 'phone' => '...', 'message' => '...']. if ( ! empty( $flags['lead_collected'] ) && ! empty( $flags['lead_data'] ) && is_array( $flags['lead_data'] ) ) { $this->process_lead_collection( $flags['lead_data'], $session_id, $context ); } // Clean any remaining LEAD_INFO tags from message (in case SaaS didn't fully clean them). $ai_message = preg_replace( '/\s*\[LEAD_INFO:[^\]]+\]\s*/', ' ', $ai_message ); $ai_message = trim( preg_replace( '/\s+/', ' ', $ai_message ) ); $bot_message_id = Agentorie_Database::insert_message( array( 'session_id' => $session_id, 'role' => 'assistant', 'message' => $ai_message, ) ); $session->increment_messages(); $session->set_last_message( $ai_message ); $session->save(); $session->sync_to_database(); $test_mode = $exclude_from_stats || ( isset( $response['test_mode'] ) && $response['test_mode'] ); $test_remaining = isset( $response['test_remaining'] ) ? (int) $response['test_remaining'] : null; $result = array( 'message' => $ai_message, 'message_id' => $bot_message_id, 'admin_takeover' => false, 'flags' => $flags, 'test_mode' => $test_mode, ); if ( $test_mode && null !== $test_remaining ) { $result['test_remaining'] = $test_remaining; } if ( isset( $response['onboarding_mode'] ) && $response['onboarding_mode'] ) { $result['onboarding_mode'] = true; } Agentorie_API_Helpers::send_success( $result ); } /** * Handle get history request. * * @return void */ public function handle_get_history() { $this->verify_nonce_or_fail(); $session_id = Agentorie_API_Helpers::get_input( 'session_id', '', 'key' ); if ( empty( $session_id ) || ! Agentorie_Session::validate_session_id( $session_id ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid session.', 'agentorie-chat' ) ); return; } $existing_session = Agentorie_Database::get_session( $session_id ); if ( ! $existing_session ) { Agentorie_API_Helpers::send_success( array( 'history' => array(), 'admin_takeover' => false, ) ); return; } $session = Agentorie_Session::get_or_create( $session_id ); $session->update_visitor_info( Agentorie_API_Helpers::get_input( 'visitor_id', '', 'text' ), Agentorie_API_Helpers::get_input( 'current_url', '', 'url' ), Agentorie_API_Helpers::get_input( 'page_title', '', 'text' ) ); $session->save(); $session->sync_to_database(); // FIX: Sync to DB so LiveChat admin sees visitor_id/current_url. $messages = Agentorie_Database::get_messages( $session_id ); Agentorie_API_Helpers::send_success( array( 'history' => Agentorie_Database::dates_to_iso8601( $messages ), 'admin_takeover' => $session->is_admin_takeover(), ) ); } /** * Handle send contact request. * * @return void */ public function handle_send_contact() { $this->verify_nonce_or_fail(); $session_id = Agentorie_API_Helpers::get_input( 'session_id', '', 'key' ); $name = Agentorie_API_Helpers::get_input( 'client_nom', '', 'text' ); $email = Agentorie_API_Helpers::get_input( 'client_email', '', 'email' ); $phone = Agentorie_API_Helpers::get_input( 'client_telephone', '', 'text' ); $message = Agentorie_API_Helpers::get_input( 'message_client', '', 'textarea' ); if ( empty( $session_id ) || empty( $name ) ) { Agentorie_API_Helpers::send_error( __( 'Name is required.', 'agentorie-chat' ) ); } if ( ! Agentorie_Session::validate_session_id( $session_id ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid session.', 'agentorie-chat' ) ); } if ( empty( $email ) && empty( $phone ) ) { Agentorie_API_Helpers::send_error( __( 'Please provide at least an email or phone number.', 'agentorie-chat' ) ); } if ( ! empty( $email ) && ! is_email( $email ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid email format.', 'agentorie-chat' ) ); } if ( ! Agentorie_API_Helpers::check_rate_limit( $session_id . '_contact', 1, 600 ) ) { Agentorie_API_Helpers::send_error( __( 'You have already sent a request recently.', 'agentorie-chat' ) ); } if ( Agentorie_API_Helpers::is_spam_content( $message ) ) { Agentorie_API_Helpers::send_error( __( 'Your message contains suspicious content.', 'agentorie-chat' ) ); } Agentorie_Database::insert_contact( array( 'session_id' => $session_id, 'name' => $name, 'email' => $email, 'phone' => $phone, 'message' => $message, ) ); Agentorie_API_Helpers::send_contact_notification( $session_id, $name, $email, $phone, $message ); Agentorie_API_Helpers::send_success( array( 'message' => __( 'Your request has been sent! We will contact you shortly.', 'agentorie-chat' ) ) ); } /** * Handle speech to text request. * * The SaaS backend handles all authorization: * - Active subscription with STT feature * - Demo mode with remaining quota * * WordPress plugin simply forwards the request. */ public function handle_speech_to_text() { $this->verify_nonce_or_fail(); // Per-IP guard on the voice path (STT and TTS share this budget; each call hits the voice API). if ( ! Agentorie_API_Helpers::check_ip_rate_limit( 'voice', Agentorie_API_Helpers::RATE_LIMIT_IP_VOICE ) ) { Agentorie_API_Helpers::send_error( __( 'Too many requests. Please slow down and try again.', 'agentorie-chat' ), 429 ); } $audio = Agentorie_API_Helpers::get_input( 'audio', '', 'base64' ); $language = Agentorie_API_Helpers::get_input( 'language', 'fr-FR', 'text' ); if ( empty( $audio ) ) { Agentorie_API_Helpers::send_error( __( 'No audio data provided.', 'agentorie-chat' ) ); } $saas = Agentorie_API_SaaS::get_instance(); $response = $saas->speech_to_text( $audio, $language ); if ( is_wp_error( $response ) ) { // SaaS returns specific error if subscription/demo quota exceeded. $error_message = $response->get_error_message(); Agentorie_API_Helpers::send_error( $error_message ? $error_message : __( 'Speech recognition failed.', 'agentorie-chat' ) ); } Agentorie_API_Helpers::send_success( array( 'text' => isset( $response['text'] ) ? $response['text'] : '' ) ); } /** * Handle text to speech request. * * The SaaS backend handles all authorization: * - Active subscription with TTS feature * - Demo mode with remaining quota * * WordPress plugin simply forwards the request. */ public function handle_text_to_speech() { $this->verify_nonce_or_fail(); // Per-IP guard on the voice path (STT and TTS share this budget; each call hits the voice API). if ( ! Agentorie_API_Helpers::check_ip_rate_limit( 'voice', Agentorie_API_Helpers::RATE_LIMIT_IP_VOICE ) ) { Agentorie_API_Helpers::send_error( __( 'Too many requests. Please slow down and try again.', 'agentorie-chat' ), 429 ); } $text = Agentorie_API_Helpers::get_input( 'text', '', 'textarea' ); $language = Agentorie_API_Helpers::get_input( 'language', 'fr-FR', 'text' ); if ( empty( $text ) ) { Agentorie_API_Helpers::send_error( __( 'No text provided.', 'agentorie-chat' ) ); } $saas = Agentorie_API_SaaS::get_instance(); $response = $saas->text_to_speech( $text, $language ); if ( is_wp_error( $response ) ) { // SaaS returns specific error if subscription/demo quota exceeded. $error_message = $response->get_error_message(); Agentorie_API_Helpers::send_error( $error_message ? $error_message : __( 'Speech synthesis failed.', 'agentorie-chat' ) ); } Agentorie_API_Helpers::send_success( array( 'audio' => isset( $response['audio'] ) ? $response['audio'] : '' ) ); } /** * Handle WebSocket token request. * * This endpoint is used by both: * - Public widget (uses agentorie_public_nonce) * - LiveChat admin (uses agentorie_admin_nonce) * * @return void */ public function handle_get_ws_token() { // Accept either public nonce (widget) or admin nonce (LiveChat). $public_nonce_check = Agentorie_API_Helpers::verify_public_nonce(); $admin_nonce_check = Agentorie_API_Helpers::verify_nonce( 'agentorie_chat_admin_nonce' ); if ( true !== $public_nonce_check && ! $admin_nonce_check ) { if ( 'expired' === $public_nonce_check ) { Agentorie_API_Helpers::send_nonce_expired(); } Agentorie_API_Helpers::send_error( __( 'Security check failed.', 'agentorie-chat' ), 403 ); return; } // Per-IP flood protection (anonymous visitors only; admins bypass). if ( ! Agentorie_API_Helpers::check_ip_rate_limit( 'global', Agentorie_API_Helpers::RATE_LIMIT_IP_GLOBAL ) ) { Agentorie_API_Helpers::send_error( __( 'Too many requests. Please slow down and try again.', 'agentorie-chat' ), 429 ); } $session_id = Agentorie_API_Helpers::get_input( 'session_id', '', 'key' ); if ( empty( $session_id ) ) { Agentorie_API_Helpers::send_error( __( 'Session ID is required.', 'agentorie-chat' ), 400 ); return; } // Check settings before making SaaS request. $license_status = agentorie_chat_get_setting( 'license_status', 'inactive' ); $license_key = agentorie_chat_get_setting( 'license_key', '' ); $ws_enabled = agentorie_chat_get_setting( 'websocket_enabled', true ); if ( 'active' !== $license_status || empty( $license_key ) || ! $ws_enabled ) { // Return success with empty token - JS handles this gracefully. Agentorie_API_Helpers::send_success( array( 'token' => null ) ); return; } $user_type = current_user_can( 'manage_options' ) ? 'admin' : 'visitor'; // Test mode is decided here, in an uncached AJAX request, rather than // read from the page HTML that page caches may serve to every visitor. $test_mode = Agentorie_API_Helpers::is_excluded_ip(); $saas = Agentorie_API_SaaS::get_instance(); $response = $saas->make_ws_token_request( $session_id, $user_type, $test_mode ); if ( is_wp_error( $response ) ) { Agentorie_API_Helpers::send_error( $response->get_error_message(), 500 ); return; } if ( empty( $response['success'] ) || empty( $response['token'] ) ) { Agentorie_API_Helpers::send_error( __( 'Failed to get WebSocket token.', 'agentorie-chat' ), 500 ); return; } Agentorie_API_Helpers::send_success( array( 'token' => $response['token'], 'ws_url' => isset( $response['ws_url'] ) ? $response['ws_url'] : 'wss://app.agentorie.com/ws', 'expires_in' => isset( $response['expires_in'] ) ? $response['expires_in'] : 86400, 'ui' => isset( $response['ui'] ) ? $response['ui'] : array(), ) ); } /** * Handle save message request (WebSocket sync). * * This endpoint is used by both: * - Public widget (uses agentorie_public_nonce) * - LiveChat admin (uses agentorie_admin_nonce) * * @return void */ public function handle_save_message() { // Accept either public nonce (widget) or admin nonce (LiveChat). $public_nonce_check = Agentorie_API_Helpers::verify_public_nonce(); $admin_nonce_check = Agentorie_API_Helpers::verify_nonce( 'agentorie_chat_admin_nonce' ); if ( true !== $public_nonce_check && ! $admin_nonce_check ) { if ( 'expired' === $public_nonce_check ) { Agentorie_API_Helpers::send_nonce_expired(); } Agentorie_API_Helpers::send_error( __( 'Security check failed.', 'agentorie-chat' ), 403 ); return; } $session_id = Agentorie_API_Helpers::get_input( 'session_id', '', 'key' ); $message = Agentorie_API_Helpers::get_input( 'message', '', 'textarea' ); $role = Agentorie_API_Helpers::get_input( 'role', 'user', 'key' ); $visitor_id = Agentorie_API_Helpers::get_input( 'visitor_id', '', 'text' ); $current_url = Agentorie_API_Helpers::get_input( 'current_url', '', 'url' ); $page_title = Agentorie_API_Helpers::get_input( 'page_title', '', 'text' ); $flags = Agentorie_API_Helpers::get_input( 'flags', array(), 'json' ); // Per-IP flood protection (anonymous visitors only; admins bypass). if ( ! Agentorie_API_Helpers::check_ip_rate_limit( 'global', Agentorie_API_Helpers::RATE_LIMIT_IP_GLOBAL ) ) { Agentorie_API_Helpers::send_error( __( 'Too many requests. Please slow down and try again.', 'agentorie-chat' ), 429 ); } // is_admin comes from the widget request, NOT from current_user_can() // The widget explicitly sets is_admin=1 only for admin messages from LiveChat // Regular bot/user messages from the widget have is_admin=0. $is_admin_param = Agentorie_API_Helpers::get_input( 'is_admin', 0, 'absint' ); // Only trust is_admin=1 if the current user actually has admin rights // This prevents visitors from forging is_admin=1. $is_admin = ( $is_admin_param && current_user_can( 'manage_options' ) ) ? 1 : 0; $admin_id = $is_admin ? get_current_user_id() : 0; if ( empty( $session_id ) || ! Agentorie_Session::validate_session_id( $session_id ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid session.', 'agentorie-chat' ) ); return; } if ( empty( $message ) ) { Agentorie_API_Helpers::send_error( __( 'Message is required.', 'agentorie-chat' ) ); return; } if ( ! in_array( $role, array( 'user', 'assistant' ), true ) ) { $role = 'user'; } $session = Agentorie_Session::get_or_create( $session_id ); // Update visitor info if provided (for user messages via WebSocket). if ( 'user' === $role && ! $is_admin ) { if ( ! empty( $visitor_id ) || ! empty( $current_url ) || ! empty( $page_title ) ) { $session->update_visitor_info( $visitor_id, $current_url, $page_title ); } } $session->save(); $session->sync_to_database(); // Sync visitor info to DB for LiveChat admin. $message_data = array( 'session_id' => $session_id, 'role' => $role, 'message' => $message, 'is_admin' => $is_admin, ); // Add admin_id only for admin messages. if ( $is_admin && $admin_id > 0 ) { $message_data['admin_id'] = $admin_id; } $message_id = Agentorie_Database::insert_message( $message_data ); if ( ! $message_id ) { Agentorie_API_Helpers::send_error( __( 'Failed to save message.', 'agentorie-chat' ), 500 ); return; } // Mark session as having admin intervention (permanent flag). if ( $is_admin ) { Agentorie_Database::update_session_field( $session_id, 'admin_intervened', 1 ); } // Process lead collection flags from WebSocket responses. // $flags is already decoded as array by get_input with 'json' type. if ( ! empty( $flags ) && 'assistant' === $role ) { if ( ! empty( $flags['lead_collected'] ) && ! empty( $flags['lead_data'] ) && is_array( $flags['lead_data'] ) ) { $context = array( 'current_url' => $current_url, 'page_title' => $page_title, ); $this->process_lead_collection( $flags['lead_data'], $session_id, $context ); } } Agentorie_API_Helpers::send_success( array( 'message_id' => $message_id ) ); } /** * Handle complete onboarding request. * * @return void */ public function handle_complete_onboarding() { $this->verify_nonce_or_fail(); $saas = Agentorie_API_SaaS::get_instance(); $result = $saas->complete_onboarding(); if ( is_wp_error( $result ) ) { Agentorie_API_Helpers::send_error( $result->get_error_message(), 500 ); return; } Agentorie_API_SaaS::invalidate_license_cache(); Agentorie_API_Helpers::send_success( array( 'completed' => true, 'message' => __( 'Onboarding completed successfully.', 'agentorie-chat' ), ) ); } /** * Handle translate request. * * Proxies widget UI text translation to SaaS backend. * SaaS uses Gemini + Redis cache for translations. * * @since 1.1.6 * @return void */ public function handle_translate() { $this->verify_nonce_or_fail(); // Per-IP guard on the translation path (each call hits the AI backend). if ( ! Agentorie_API_Helpers::check_ip_rate_limit( 'translate', Agentorie_API_Helpers::RATE_LIMIT_IP_TRANSLATE ) ) { Agentorie_API_Helpers::send_error( __( 'Too many requests. Please slow down and try again.', 'agentorie-chat' ), 429 ); } $target_lang = Agentorie_API_Helpers::get_input( 'target_lang', '', 'text' ); $texts = Agentorie_API_Helpers::get_input( 'texts', array(), 'json' ); if ( empty( $target_lang ) ) { Agentorie_API_Helpers::send_error( __( 'Target language is required.', 'agentorie-chat' ) ); } // Validate BCP-47 format. if ( ! preg_match( '/^[a-z]{2,3}(-[A-Z][a-zA-Z]{1,7})?$/i', $target_lang ) ) { Agentorie_API_Helpers::send_error( __( 'Invalid language format.', 'agentorie-chat' ) ); } if ( ! is_array( $texts ) || empty( $texts ) ) { Agentorie_API_Helpers::send_error( __( 'No texts to translate.', 'agentorie-chat' ) ); } // Sanitize all text values. $clean_texts = array(); foreach ( $texts as $key => $value ) { $clean_key = sanitize_key( $key ); $clean_value = sanitize_text_field( $value ); if ( ! empty( $clean_key ) && ! empty( $clean_value ) ) { $clean_texts[ $clean_key ] = $clean_value; } } if ( empty( $clean_texts ) ) { Agentorie_API_Helpers::send_error( __( 'No valid texts to translate.', 'agentorie-chat' ) ); } $saas = Agentorie_API_SaaS::get_instance(); $response = $saas->translate_texts( $target_lang, $clean_texts ); if ( is_wp_error( $response ) ) { $error_message = $response->get_error_message(); Agentorie_API_Helpers::send_error( $error_message ? $error_message : __( 'Translation failed.', 'agentorie-chat' ) ); } $translations = isset( $response['translations'] ) ? $response['translations'] : array(); Agentorie_API_Helpers::send_success( array( 'translations' => $translations ) ); } /** * Process lead collection from AI response. * * Accepts lead data from both formats: * - Legacy: ['name', 'email', 'phone', 'request', 'notes'] * - New SaaS: ['name', 'email', 'phone', 'message'] * * @param array $lead_data Lead data. * @param string $session_id Session ID. * @param array $context Context data. */ private function process_lead_collection( $lead_data, $session_id, $context ) { $lead_email = agentorie_chat_get_setting( 'lead_collection_email', '' ); if ( empty( $lead_email ) ) { $lead_email = get_option( 'admin_email' ); } $name = isset( $lead_data['name'] ) ? sanitize_text_field( $lead_data['name'] ) : ''; $email = isset( $lead_data['email'] ) ? sanitize_email( $lead_data['email'] ) : ''; $phone = isset( $lead_data['phone'] ) ? sanitize_text_field( $lead_data['phone'] ) : ''; // Support both 'request' (legacy) and 'message' (new SaaS format). $request = isset( $lead_data['request'] ) ? sanitize_text_field( $lead_data['request'] ) : ''; if ( empty( $request ) && isset( $lead_data['message'] ) ) { $request = sanitize_text_field( $lead_data['message'] ); } $notes = isset( $lead_data['notes'] ) ? sanitize_textarea_field( $lead_data['notes'] ) : ''; $contact_data = array( 'session_id' => $session_id, 'name' => $name, 'email' => $email, 'phone' => $phone, 'message' => $request . ( $notes ? "\n\nNotes: " . $notes : '' ), ); Agentorie_Database::insert_contact( $contact_data ); $site_name = get_bloginfo( 'name' ); /* translators: %s: site name */ $subject = sprintf( __( '[%s] New Lead Collected', 'agentorie-chat' ), $site_name ); $body = __( "A new lead has been collected by your chatbot.\n\n", 'agentorie-chat' ); if ( $name ) { /* translators: %s: contact name */ $body .= sprintf( __( "Name: %s\n", 'agentorie-chat' ), $name ); } if ( $email ) { /* translators: %s: contact email */ $body .= sprintf( __( "Email: %s\n", 'agentorie-chat' ), $email ); } if ( $phone ) { /* translators: %s: contact phone */ $body .= sprintf( __( "Phone: %s\n", 'agentorie-chat' ), $phone ); } if ( $request ) { /* translators: %s: contact request */ $body .= sprintf( __( "\nRequest: %s\n", 'agentorie-chat' ), $request ); } if ( $notes ) { /* translators: %s: additional notes */ $body .= sprintf( __( "\nAdditional Notes: %s\n", 'agentorie-chat' ), $notes ); } $body .= "\n---\n"; /* translators: %s: page URL */ $body .= sprintf( __( "Page: %s\n", 'agentorie-chat' ), isset( $context['current_url'] ) ? $context['current_url'] : 'N/A' ); /* translators: %s: session ID */ $body .= sprintf( __( "Session: %s\n", 'agentorie-chat' ), $session_id ); /* translators: %s: date and time */ $body .= sprintf( __( "Date: %s\n", 'agentorie-chat' ), wp_date( 'Y-m-d H:i:s' ) ); $admin_url = admin_url( 'admin.php?page=agentorie-chat-livechat' ); /* translators: %s: admin URL to view conversation */ $body .= sprintf( __( "\nView conversation: %s\n", 'agentorie-chat' ), $admin_url ); $headers = array( 'Content-Type: text/plain; charset=UTF-8' ); if ( $email ) { $headers[] = 'Reply-To: ' . $name . ' <' . $email . '>'; } wp_mail( $lead_email, $subject, $body, $headers ); Agentorie_Logger::info( 'Lead collected', array( 'session_id' => $session_id, 'name' => $name, 'email' => $email, 'phone' => $phone, 'sent_to' => $lead_email, ) ); } }