init_hooks(); } /** * Initialize hooks. * * @since 1.0.0 * @return void */ private function init_hooks() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_styles' ) ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'wp_footer', array( $this, 'render_widget' ) ); } /** * Check if widget should be displayed. * * @since 1.0.0 * @return bool True if widget should display. */ public function should_display_widget() { if ( ! agentorie_chat_is_enabled() ) { return false; } // Admin Only mode - only show to logged-in administrators. if ( agentorie_chat_get_setting( 'admin_only', false ) && ! current_user_can( 'manage_options' ) ) { return false; } if ( is_admin() ) { return false; } // Hide on the WordPress login and registration screens (checked via $pagenow for 5.8 compatibility). if ( isset( $GLOBALS['pagenow'] ) && in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ), true ) ) { return false; } if ( is_customize_preview() ) { return false; } if ( wp_is_mobile() && agentorie_chat_get_setting( 'hide_on_mobile', false ) ) { return false; } if ( $this->is_excluded_page() ) { return false; } return apply_filters( 'agentorie_chat_display_widget', true ); } /** * Check if current page is excluded. * * @since 1.0.0 * @return bool True if page is excluded. */ private function is_excluded_page() { $excluded_pages = agentorie_chat_get_setting( 'exclude_pages', '' ); if ( empty( $excluded_pages ) ) { return false; } $paths = array_map( 'trim', explode( ',', $excluded_pages ) ); $paths = array_filter( $paths ); if ( empty( $paths ) ) { return false; } $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $current_path = wp_parse_url( $request_uri, PHP_URL_PATH ); foreach ( $paths as $path ) { if ( $current_path === $path ) { return true; } if ( '*' === substr( $path, -1 ) ) { $prefix = rtrim( $path, '*' ); if ( 0 === strpos( $current_path, $prefix ) ) { return true; } } } return false; } /** * Enqueue frontend styles. * * @since 1.0.0 * @return void */ public function enqueue_styles() { if ( ! $this->should_display_widget() ) { return; } wp_enqueue_style( 'agentorie-chat-widget', AGENTORIE_CHAT_PLUGIN_URL . 'assets/css/widget.css', array(), AGENTORIE_CHAT_VERSION ); // Add CSP nonce for inline styles if available. $csp_nonce = apply_filters( 'agentorie_chat_csp_nonce', '' ); if ( ! empty( $csp_nonce ) ) { wp_style_add_data( 'agentorie-chat-widget', 'nonce', $csp_nonce ); } wp_add_inline_style( 'agentorie-chat-widget', $this->get_appearance_css() ); } /** * Get appearance CSS variables. * * @since 1.0.0 * @return string CSS custom properties. */ private function get_appearance_css() { $primary_color = agentorie_chat_get_setting( 'primary_color', '#6366f1' ); $primary_text_color = agentorie_chat_get_setting( 'primary_text_color', '#ffffff' ); $user_bubble_color = agentorie_chat_get_setting( 'user_bubble_color', '#6366f1' ); $user_text_color = agentorie_chat_get_setting( 'user_text_color', '#ffffff' ); $bot_bubble_color = agentorie_chat_get_setting( 'bot_bubble_color', '#f3f4f6' ); $bot_text_color = agentorie_chat_get_setting( 'bot_text_color', '#1f2937' ); $background_color = agentorie_chat_get_setting( 'background_color', '#ffffff' ); $primary_dark = $this->darken_color( $primary_color, 15 ); $button_sizes = array( 'small' => '50px', 'medium' => '60px', 'large' => '70px', ); $button_size = agentorie_chat_get_setting( 'button_size', 'medium' ); $button_px = isset( $button_sizes[ $button_size ] ) ? $button_sizes[ $button_size ] : '60px'; $chat_width = agentorie_chat_get_setting( 'chat_width', '380' ); $chat_height = agentorie_chat_get_setting( 'chat_height', '550' ); $border_radius = agentorie_chat_get_setting( 'border_radius', '16' ); $shadows = array( 'none' => 'none', 'light' => '0 4px 20px rgba(0, 0, 0, 0.08)', 'medium' => '0 10px 40px rgba(0, 0, 0, 0.15)', 'strong' => '0 20px 60px rgba(0, 0, 0, 0.25)', ); $shadow_style = agentorie_chat_get_setting( 'shadow_style', 'medium' ); $shadow = isset( $shadows[ $shadow_style ] ) ? $shadows[ $shadow_style ] : $shadows['medium']; $button_margin_bottom = agentorie_chat_get_setting( 'button_margin_bottom', '20' ); $welcome_font_weight = agentorie_chat_get_setting( 'welcome_font_weight', '600' ); $css = ':root { --agentorie-primary: ' . esc_attr( $primary_color ) . '; --agentorie-primary-dark: ' . esc_attr( $primary_dark ) . '; --agentorie-primary-text: ' . esc_attr( $primary_text_color ) . '; --agentorie-user-bubble: ' . esc_attr( $user_bubble_color ) . '; --agentorie-user-text: ' . esc_attr( $user_text_color ) . '; --agentorie-bot-bubble: ' . esc_attr( $bot_bubble_color ) . '; --agentorie-bot-text: ' . esc_attr( $bot_text_color ) . '; --agentorie-background: ' . esc_attr( $background_color ) . '; --agentorie-button-size: ' . esc_attr( $button_px ) . '; --agentorie-button-margin-bottom: ' . esc_attr( $button_margin_bottom ) . 'px; --agentorie-chat-width: ' . esc_attr( $chat_width ) . 'px; --agentorie-chat-height: ' . esc_attr( $chat_height ) . ( is_numeric( $chat_height ) ? 'px' : '' ) . '; --agentorie-border-radius: ' . esc_attr( $border_radius ) . 'px; --agentorie-shadow: ' . esc_attr( $shadow ) . '; --agentorie-welcome-weight: ' . esc_attr( $welcome_font_weight ) . '; }'; return $css; } /** * Darken a hex color by percentage. * * @since 1.0.0 * @param string $hex Hex color code. * @param int $percent Percentage to darken. * @return string Darkened hex color. */ private function darken_color( $hex, $percent ) { $hex = ltrim( $hex, '#' ); if ( 6 !== strlen( $hex ) ) { return '#' . $hex; } $r = hexdec( substr( $hex, 0, 2 ) ); $g = hexdec( substr( $hex, 2, 2 ) ); $b = hexdec( substr( $hex, 4, 2 ) ); $r = max( 0, (int) floor( $r * ( 1 - $percent / 100 ) ) ); $g = max( 0, (int) floor( $g * ( 1 - $percent / 100 ) ) ); $b = max( 0, (int) floor( $b * ( 1 - $percent / 100 ) ) ); return sprintf( '#%02x%02x%02x', $r, $g, $b ); } /** * Enqueue frontend scripts. * * @since 1.0.0 * @return void */ public function enqueue_scripts() { if ( ! $this->should_display_widget() ) { return; } wp_enqueue_script( 'agentorie-chat-widget', AGENTORIE_CHAT_PLUGIN_URL . 'assets/js/widget.js', array(), AGENTORIE_CHAT_VERSION, array( 'in_footer' => true, 'strategy' => 'defer', ) ); // Add CSP nonce if available (for sites with strict Content-Security-Policy). $csp_nonce = apply_filters( 'agentorie_chat_csp_nonce', '' ); if ( ! empty( $csp_nonce ) ) { wp_script_add_data( 'agentorie-chat-widget', 'nonce', $csp_nonce ); } wp_localize_script( 'agentorie-chat-widget', 'agentorieWidget', $this->get_widget_config() ); // The real-time scripts (Socket.io and the WebSocket module) are not enqueued here: // widget.js loads them from the plugin folder only when they are needed, that is // when the visitor opens the chat or already has a conversation in progress. } /** * Get widget configuration for JavaScript. * * @since 1.0.0 * @return array Widget configuration. */ private function get_widget_config() { // Voice features - based on user settings only. // SaaS handles authorization (subscription or demo). $stt_enabled = (bool) agentorie_chat_get_setting( 'speech_to_text', false ); $tts_enabled = (bool) agentorie_chat_get_setting( 'text_to_speech', false ); // Build authorized languages list (BCP-47 codes). $default_lang = agentorie_chat_get_setting( 'default_language', 'en-US' ); $authorized_languages = array( $default_lang ); // Add languages from pack (active_languages) or legacy individual features (lang_XX-YY). $active_languages_setting = agentorie_chat_get_setting( 'active_languages', array() ); if ( is_array( $active_languages_setting ) && ! empty( $active_languages_setting ) ) { foreach ( $active_languages_setting as $lang_code ) { if ( ! in_array( $lang_code, $authorized_languages, true ) ) { $authorized_languages[] = $lang_code; } } } else { $license_features = agentorie_chat_get_setting( 'license_features', array() ); if ( is_array( $license_features ) ) { foreach ( $license_features as $feature ) { if ( 0 === strpos( $feature, 'lang_' ) && 0 !== strpos( $feature, 'lang_pack_' ) ) { $lang_code = substr( $feature, 5 ); if ( ! in_array( $lang_code, $authorized_languages, true ) ) { $authorized_languages[] = $lang_code; } } } } } // Add demo language if active AND demos remaining. $demo_lang_active = agentorie_chat_get_setting( 'demo_lang_active', '' ); $demo_lang_remaining = (int) agentorie_chat_get_setting( 'demo_lang_remaining', 0 ); if ( ! empty( $demo_lang_active ) && $demo_lang_remaining > 0 && ! in_array( $demo_lang_active, $authorized_languages, true ) ) { $authorized_languages[] = $demo_lang_active; } // Build labels and badges for authorized languages only. $all_languages = Agentorie_Admin::get_supported_languages(); $language_labels = array(); $language_badges = array(); foreach ( $authorized_languages as $code ) { $language_labels[ $code ] = isset( $all_languages[ $code ] ) ? $all_languages[ $code ] : $code; $parts = explode( '-', $code ); $language_badges[ $code ] = strtoupper( end( $parts ) ); } // WebSocket configuration. $websocket_enabled = agentorie_chat_get_setting( 'websocket_enabled', true ); // Test mode: admin users get exclude_from_stats = true. $is_excluded_ip = Agentorie_API_Helpers::is_excluded_ip(); // SaaS URL for translate endpoint. $saas_url = defined( 'AGENTORIE_CHAT_SAAS_URL' ) ? AGENTORIE_CHAT_SAAS_URL : 'https://app.agentorie.com'; return array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'restUrl' => esc_url_raw( rest_url( 'agentorie-chat/v1/' ) ), 'saasUrl' => esc_url_raw( $saas_url ), 'nonce' => wp_create_nonce( 'agentorie_chat_public_nonce' ), 'rateLimit' => (int) agentorie_chat_get_setting( 'rate_limit', 10 ), 'authorizedLanguages' => array_values( $authorized_languages ), 'languageLabels' => $language_labels, 'languageBadges' => $language_badges, 'isExcludedIP' => $is_excluded_ip, 'config' => array( 'botName' => agentorie_chat_get_setting( 'bot_name', 'Stella' ), 'botSubtitle' => agentorie_chat_get_setting( 'bot_subtitle', __( 'Virtual Assistant', 'agentorie-chat' ) ), 'botAvatar' => agentorie_chat_get_setting( 'bot_avatar', '' ), 'adminName' => agentorie_chat_get_setting( 'livechat_advisor_name', __( 'Advisor', 'agentorie-chat' ) ), 'adminAvatar' => agentorie_chat_get_setting( 'livechat_advisor_avatar', '👤' ), 'welcomeTitle' => agentorie_chat_get_setting( 'welcome_title', __( 'Hello 👋', 'agentorie-chat' ) ), 'welcomeMessage' => agentorie_chat_get_setting( 'welcome_message', __( 'I am here to answer your questions.', 'agentorie-chat' ) ), 'position' => agentorie_chat_get_setting( 'widget_position', 'bottom-right' ), 'displayDelay' => absint( agentorie_chat_get_setting( 'widget_delay_seconds', 0 ) ) * 1000, 'conversationRetentionHours' => absint( agentorie_chat_get_setting( 'conversation_retention_hours', 6 ) ), ), 'appearance' => array( 'buttonIcon' => agentorie_chat_get_setting( 'button_icon', '💬' ), 'buttonStyle' => agentorie_chat_get_setting( 'button_style', 'solid' ), 'buttonEffect' => agentorie_chat_get_setting( 'button_effect', 'none' ), 'buttonAnimation' => agentorie_chat_get_setting( 'button_animation', 'pulse' ), 'headerStyle' => agentorie_chat_get_setting( 'header_style', 'solid' ), 'headerEffect' => agentorie_chat_get_setting( 'header_effect', 'none' ), 'showHeaderAvatar' => (bool) agentorie_chat_get_setting( 'show_header_avatar', true ), 'showClientAvatar' => (bool) agentorie_chat_get_setting( 'show_client_avatar', false ), 'clientAvatar' => agentorie_chat_get_setting( 'client_avatar', '👤' ), 'showLangSelector' => (bool) agentorie_chat_get_setting( 'show_lang_selector', true ), ), 'voice' => array( 'enabled' => $stt_enabled || $tts_enabled, 'sttEnabled' => $stt_enabled, 'ttsEnabled' => $tts_enabled, 'autoSend' => true, 'silenceDetection' => true, 'silenceDuration' => 1500, 'defaultLanguage' => agentorie_chat_get_setting( 'default_language', 'fr-FR' ), ), 'websocket' => array( 'enabled' => $websocket_enabled, 'scripts' => $websocket_enabled ? array( add_query_arg( 'ver', '4.8.2', AGENTORIE_CHAT_PLUGIN_URL . 'assets/js/vendor/socket.io.min.js' ), add_query_arg( 'ver', AGENTORIE_CHAT_VERSION, AGENTORIE_CHAT_PLUGIN_URL . 'assets/js/widget-websocket.js' ), ) : array(), 'nonce' => (string) apply_filters( 'agentorie_chat_csp_nonce', '' ), ), 'strings' => $this->get_widget_strings(), 'i18n' => $this->get_i18n_strings(), 'sourceTexts' => array( 'bot_subtitle' => agentorie_chat_get_setting( 'bot_subtitle', __( 'Virtual Assistant', 'agentorie-chat' ) ), 'availability_text' => agentorie_chat_get_setting( 'availability_text', __( 'Available 24/7', 'agentorie-chat' ) ), 'welcome_title' => agentorie_chat_get_setting( 'welcome_title', __( 'Hello 👋', 'agentorie-chat' ) ), 'welcome_message' => agentorie_chat_get_setting( 'welcome_message', __( 'I am here to answer your questions.', 'agentorie-chat' ) ), 'placeholder' => agentorie_chat_get_setting( 'input_placeholder', __( 'Type your message...', 'agentorie-chat' ) ), 'placeholder_voice' => agentorie_chat_get_setting( 'voice_placeholder', __( 'Speak or write your message...', 'agentorie-chat' ) ), 'reset_title' => __( 'Reset conversation', 'agentorie-chat' ), 'change_language' => __( 'Change language', 'agentorie-chat' ), 'record_voice_title' => __( 'Record voice message', 'agentorie-chat' ), 'select_language' => agentorie_chat_get_setting( 'lang_selector_title', __( 'Select your language', 'agentorie-chat' ) ), ), ); } /** * Get widget-specific strings for JavaScript. * * @since 1.0.0 * @return array Strings for widget JS. */ private function get_widget_strings() { return array( 'errorGeneric' => __( 'Sorry, an error occurred. Please try again.', 'agentorie-chat' ), 'errorConnection' => __( 'Connection error. Please check your internet connection.', 'agentorie-chat' ), 'rateLimitError' => __( 'Too many messages. Please wait {seconds} seconds.', 'agentorie-chat' ), 'reconnecting' => __( 'Reconnecting...', 'agentorie-chat' ), /* translators: %1$s: admin display name, %2$s: bot/assistant name */ 'releaseNotice' => __( '%1$s has handed you back to %2$s', 'agentorie-chat' ), 'typeMessage' => __( 'Type your message...', 'agentorie-chat' ), 'takeControlFirst' => __( 'Take control to send messages...', 'agentorie-chat' ), /* translators: %s: admin display name who took over the conversation */ 'takeoverNotice' => __( '%s is now assisting you', 'agentorie-chat' ), 'finishSetup' => __( 'Finish Setup', 'agentorie-chat' ), 'onboardingComplete' => __( 'Setup complete! Your chatbot is now ready for visitors.', 'agentorie-chat' ), ); } /** * Get i18n strings for JavaScript. * * @since 1.0.0 * @return array Internationalization strings. */ private function get_i18n_strings() { return array( 'placeholder' => __( 'Type your message...', 'agentorie-chat' ), 'send' => __( 'Send', 'agentorie-chat' ), 'close' => __( 'Close', 'agentorie-chat' ), 'minimize' => __( 'Minimize', 'agentorie-chat' ), 'newConversation' => __( 'New conversation', 'agentorie-chat' ), 'advisorTakeover' => __( 'An advisor is now assisting you', 'agentorie-chat' ), 'connectionError' => __( 'Connection error. Please try again.', 'agentorie-chat' ), 'rateLimited' => __( 'Please wait a moment before sending another message.', 'agentorie-chat' ), 'rateLimitError' => __( 'Too many messages. Please wait {seconds} seconds.', 'agentorie-chat' ), 'limitReached' => __( 'Message limit reached. Please try again later.', 'agentorie-chat' ), 'conversationLimit' => __( 'This conversation has reached its message limit. Please start a new conversation.', 'agentorie-chat' ), 'startRecording' => __( 'Start recording', 'agentorie-chat' ), 'stopRecording' => __( 'Stop recording', 'agentorie-chat' ), 'listening' => __( 'Listening...', 'agentorie-chat' ), 'processing' => __( 'Processing...', 'agentorie-chat' ), 'listenMessage' => __( 'Listen to message', 'agentorie-chat' ), 'justNow' => __( 'Just now', 'agentorie-chat' ), 'selectLanguage' => __( 'Select your language', 'agentorie-chat' ), /* translators: %d: number of minutes ago */ 'minutesAgo' => __( '%d min ago', 'agentorie-chat' ), 'contactForm' => array( 'title' => __( 'Contact Us', 'agentorie-chat' ), 'name' => __( 'Your name', 'agentorie-chat' ), 'email' => __( 'Your email', 'agentorie-chat' ), 'phone' => __( 'Your phone (optional)', 'agentorie-chat' ), 'message' => __( 'Your message', 'agentorie-chat' ), 'submit' => __( 'Send', 'agentorie-chat' ), 'success' => __( 'Thank you! We will contact you soon.', 'agentorie-chat' ), 'error' => __( 'An error occurred. Please try again.', 'agentorie-chat' ), ), ); } /** * Render the chat widget. * * @since 1.0.0 * @return void */ public function render_widget() { if ( ! $this->should_display_widget() ) { return; } $bot_name = agentorie_chat_get_setting( 'bot_name', 'Stella' ); $bot_subtitle = agentorie_chat_get_setting( 'bot_subtitle', __( 'Virtual Assistant', 'agentorie-chat' ) ); $bot_avatar = agentorie_chat_get_setting( 'bot_avatar', '' ); $position = agentorie_chat_get_setting( 'widget_position', 'bottom-right' ); $primary_color = agentorie_chat_get_setting( 'widget_primary_color', '#6366f1' ); $text_color = agentorie_chat_get_setting( 'widget_text_color', '#ffffff' ); $stt_enabled = (bool) agentorie_chat_get_setting( 'speech_to_text', false ); $tts_enabled = (bool) agentorie_chat_get_setting( 'text_to_speech', false ); $template = locate_template( 'agentorie-chat/widget.php' ); try { if ( $template ) { include $template; } else { include AGENTORIE_CHAT_PLUGIN_DIR . 'assets/template-widget.php'; } } catch ( Exception $e ) { Agentorie_Logger::error( 'Widget render failed', array( 'error' => $e->getMessage(), 'template' => $template ? $template : 'default', ) ); } } }