| @@ -58,8 +58,62 @@ | ||
| 58 | 58 | }); |
| 59 | 59 | </script> |
| 60 | 60 | <?php |
| 61 | 61 | } |
| 62 | +/** | |
| 63 | + * Extract a YouTube video ID from common URL formats. | |
| 64 | + * | |
| 65 | + * @param string $url YouTube watch, embed, short, or youtu.be URL. | |
| 66 | + * @return string Video ID or empty string. | |
| 67 | + */ | |
| 68 | +if ( ! function_exists( 'qcld_wpbot_extract_youtube_id' ) ) { | |
| 69 | + function qcld_wpbot_extract_youtube_id( $url ) { | |
| 70 | + $url = trim( (string) $url ); | |
| 71 | + if ( $url === '' ) { | |
| 72 | + return ''; | |
| 73 | + } | |
| 74 | + | |
| 75 | + if ( preg_match( '/(?:youtube\.com\/(?:embed\/|shorts\/|live\/|watch\?(?:.*&)?v=)|youtu\.be\/)([A-Za-z0-9_-]{11})/', $url, $matches ) ) { | |
| 76 | + return $matches[1]; | |
| 77 | + } | |
| 78 | + | |
| 79 | + $path = (string) wp_parse_url( $url, PHP_URL_PATH ); | |
| 80 | + $base = basename( $path ); | |
| 81 | + if ( preg_match( '/^[A-Za-z0-9_-]{11}$/', $base ) ) { | |
| 82 | + return $base; | |
| 83 | + } | |
| 84 | + | |
| 85 | + return ''; | |
| 86 | + } | |
| 87 | +} | |
| 88 | +if ( ! function_exists( 'qcld_wpbot_youtube_icon_embed_src' ) ) { | |
| 89 | + function qcld_wpbot_youtube_icon_embed_src( $url ) { | |
| 90 | + $video_id = qcld_wpbot_extract_youtube_id( $url ); | |
| 91 | + if ( $video_id === '' ) { | |
| 92 | + return ''; | |
| 93 | + } | |
| 94 | + | |
| 95 | + return add_query_arg( | |
| 96 | + array( | |
| 97 | + 'autoplay' => '1', | |
| 98 | + 'mute' => '1', | |
| 99 | + 'loop' => '1', | |
| 100 | + 'playlist' => $video_id, | |
| 101 | + 'controls' => '0', | |
| 102 | + 'showinfo' => '0', | |
| 103 | + 'rel' => '0', | |
| 104 | + 'fs' => '0', | |
| 105 | + 'iv_load_policy' => '3', | |
| 106 | + 'cc_load_policy' => '0', | |
| 107 | + 'disablekb' => '1', | |
| 108 | + 'playsinline' => '1', | |
| 109 | + 'modestbranding' => '1', | |
| 110 | + 'color' => 'white', | |
| 111 | + ), | |
| 112 | + 'https://www.youtube.com/embed/' . rawurlencode( $video_id ) | |
| 113 | + ); | |
| 114 | + } | |
| 115 | +} | |
| 62 | 116 | function wp_chatbot_load_footer_html(){ |
| 63 | 117 | if ( get_option('disable_wp_chatbot') != 1 && wp_chatbot_load_controlling() === true) { |
| 64 | 118 | |
| 65 | 119 | ?> |
| @@ -79,10 +133,27 @@ | ||
| 79 | 133 | } |
| 80 | 134 | ?> |
| 81 | 135 | <style> |
| 82 | 136 | .wp-chatbot-container { |
| 137 | + background-color: #eceef3 !important; | |
| 83 | 138 | background-image: url(<?php echo esc_url($qcld_wb_chatbot_board_bg_path); ?>) !important; |
| 139 | + background-size: cover !important; | |
| 140 | + background-position: center !important; | |
| 141 | + background-repeat: no-repeat !important; | |
| 84 | 142 | } |
| 143 | + .wp-chatbot-template-01 #wp-chatbot-board-container, | |
| 144 | + .wp-chatbot-template-01 .wp-chatbot-board-container { | |
| 145 | + background-color: #eceef3 !important; | |
| 146 | + background-image: none !important; | |
| 147 | + } | |
| 148 | + .wp-chatbot-template-01 #wp-chatbot-board-container::before, | |
| 149 | + .wp-chatbot-template-01 .wp-chatbot-board-container::before { | |
| 150 | + background-color: #eceef3 !important; | |
| 151 | + background-image: url(<?php echo esc_url($qcld_wb_chatbot_board_bg_path); ?>) !important; | |
| 152 | + background-size: cover !important; | |
| 153 | + background-position: center !important; | |
| 154 | + background-repeat: no-repeat !important; | |
| 155 | + } | |
| 85 | 156 | </style> |
| 86 | 157 | <?php } |
| 87 | 158 | $wp_chatbot_enable_rtl = ""; |
| 88 | 159 | if (get_option('enable_wp_chatbot_rtl') == '1') { |
| @@ -180,14 +251,72 @@ | ||
| 180 | 251 | $wp_chatbot_custom_icon_path = QCLD_wpCHATBOT_IMG_URL . get_option('wp_chatbot_icon'); |
| 181 | 252 | } else { |
| 182 | 253 | $wp_chatbot_custom_icon_path = QCLD_wpCHATBOT_IMG_URL . 'custom.png'; |
| 183 | 254 | } |
| 255 | + $_wpbot_icon_video = get_option('wp_chatbot_icon_video', ''); | |
| 256 | + $_wpbot_video_is_youtube = ( strpos( $_wpbot_icon_video, 'youtube.com' ) !== false || strpos( $_wpbot_icon_video, 'youtu.be' ) !== false ); | |
| 257 | + $_wpbot_youtube_embed_src = $_wpbot_video_is_youtube ? qcld_wpbot_youtube_icon_embed_src( $_wpbot_icon_video ) : ''; | |
| 258 | + $_wpbot_video_delay_ms = absint( get_option( 'wp_chatbot_icon_video_delay', 0 ) ) * 1000; | |
| 259 | + | |
| 260 | + $wp_chatbot_ball_is_youtube = ($_wpbot_icon_video !== '' && (strpos($_wpbot_icon_video, 'youtube.com') !== false || strpos($_wpbot_icon_video, 'youtu.be') !== false)); | |
| 261 | + $wp_chatbot_ball_is_video = ($_wpbot_icon_video !== '' && !$wp_chatbot_ball_is_youtube); | |
| 262 | + $wp_chatbot_ball_has_video = ($wp_chatbot_ball_is_youtube || $wp_chatbot_ball_is_video); | |
| 184 | 263 | ?> |
| 185 | 264 | <img src="<?php echo esc_url($wp_chatbot_custom_icon_path); ?>" |
| 186 | - alt="wpChatIcon" qcld_agent="<?php echo esc_url($wp_chatbot_custom_icon_path); ?>" > | |
| 187 | - | |
| 265 | + alt="wpChatIcon" qcld_agent="<?php echo esc_url($wp_chatbot_custom_icon_path); ?>" | |
| 266 | + id="wp-chatbot-ball-icon-img" | |
| 267 | + <?php if ($wp_chatbot_ball_has_video) { echo 'style="display:none;"'; } ?> > | |
| 268 | + <?php if ( $_wpbot_icon_video !== '' ) : ?> | |
| 269 | + <?php if ( $_wpbot_video_is_youtube && $_wpbot_youtube_embed_src !== '' ) : ?> | |
| 270 | + <iframe class="wpbot-icon-video" src="<?php echo $_wpbot_video_delay_ms > 0 ? 'about:blank' : esc_url( $_wpbot_youtube_embed_src ); ?>" data-wpbot-yt-src="<?php echo esc_url( $_wpbot_youtube_embed_src ); ?>" frameborder="0" allow="autoplay; fullscreen; encrypted-media; picture-in-picture"></iframe> | |
| 271 | + <?php elseif ( ! $_wpbot_video_is_youtube ) : ?> | |
| 272 | + <video class="wpbot-icon-video" src="<?php echo esc_url( $_wpbot_icon_video ); ?>" autoplay muted loop playsinline preload="auto"></video> | |
| 273 | + <?php endif; ?> | |
| 274 | + <?php endif; ?> | |
| 188 | 275 | </div> |
| 276 | + | |
| 189 | 277 | </div> |
| 278 | + <?php | |
| 279 | + if ( $_wpbot_icon_video !== '' ) : | |
| 280 | + ?> | |
| 281 | + <script> | |
| 282 | + (function(){ | |
| 283 | + var wpbotDelay = <?php echo (int) $_wpbot_video_delay_ms; ?>; | |
| 284 | + function wpbotForcePlay(){ | |
| 285 | + var v = document.querySelector('#wp-chatbot-ball video.wpbot-icon-video'); | |
| 286 | + if( v ){ | |
| 287 | + v.muted = true; | |
| 288 | + v.volume = 0; | |
| 289 | + v.loop = true; | |
| 290 | + var tries = 0, maxTries = 30; | |
| 291 | + var timer = setInterval(function(){ | |
| 292 | + tries++; | |
| 293 | + v.play().then(function(){ clearInterval(timer); }).catch(function(){}); | |
| 294 | + if( tries >= maxTries ) clearInterval(timer); | |
| 295 | + }, 300); | |
| 296 | + } | |
| 297 | + var yt = document.querySelector('#wp-chatbot-ball iframe.wpbot-icon-video'); | |
| 298 | + if( yt ){ | |
| 299 | + var ytSrc = yt.getAttribute('data-wpbot-yt-src'); | |
| 300 | + if( ytSrc && ( !yt.getAttribute('src') || yt.getAttribute('src') === 'about:blank' || yt.getAttribute('src').indexOf('autoplay=1') === -1 ) ){ | |
| 301 | + yt.setAttribute('src', ytSrc); | |
| 302 | + } | |
| 303 | + } | |
| 304 | + } | |
| 305 | + function wpbotDelayedPlay(){ | |
| 306 | + setTimeout(wpbotForcePlay, wpbotDelay); | |
| 307 | + } | |
| 308 | + if( document.readyState === 'loading' ){ | |
| 309 | + document.addEventListener('DOMContentLoaded', wpbotDelayedPlay); | |
| 310 | + } else { | |
| 311 | + wpbotDelayedPlay(); | |
| 312 | + } | |
| 313 | + window.addEventListener('load', function(){ | |
| 314 | + setTimeout(wpbotForcePlay, wpbotDelay); | |
| 315 | + }); | |
| 316 | + })(); | |
| 317 | + </script> | |
| 318 | + <?php endif; ?> | |
| 190 | 319 | <?php |
| 191 | 320 | $fb_app_id = get_option('qlcd_wp_chatbot_fb_app_id'); |
| 192 | 321 | $fb_page_id = get_option('qlcd_wp_chatbot_fb_page_id'); |
| 193 | 322 | $fb_mgs_color = get_option('qlcd_wp_chatbot_fb_color') != '' ? get_option('qlcd_wp_chatbot_fb_color') : '#0084ff'; |
| @@ -1806,15 +1935,68 @@ | ||
| 1806 | 1935 | } |
| 1807 | 1936 | |
| 1808 | 1937 | } |
| 1809 | 1938 | |
| 1939 | +/** | |
| 1940 | + * Safely sanitize chatbot conversation input. | |
| 1941 | + * | |
| 1942 | + * SECURITY FIX (CVE WPBot Stored XSS ≤ 8.6.9): | |
| 1943 | + * The previous order was: wp_kses() → html_entity_decode() → htmlspecialchars(). | |
| 1944 | + * An attacker could submit entity-encoded payloads (<img onerror=...>) that | |
| 1945 | + * bypassed wp_kses (which saw inert text), were then decoded back into live markup | |
| 1946 | + * by html_entity_decode(), and survived into storage and the admin UI. | |
| 1947 | + * | |
| 1948 | + * Correct order: html_entity_decode() FIRST → wp_kses() → htmlspecialchars(). | |
| 1949 | + * wp_kses() now sees the real decoded markup and strips forbidden tags/attributes. | |
| 1950 | + * | |
| 1951 | + * @param string $data Raw conversation string (already wp_unslash'd by caller). | |
| 1952 | + * @return string Sanitized, entity-encoded string safe for DB storage. | |
| 1953 | + */ | |
| 1810 | 1954 | function qcld_wpbot_input_validation( $data ) { |
| 1811 | - $data = html_entity_decode($data); | |
| 1812 | - $data = trim($data); | |
| 1813 | - $data = stripslashes($data); | |
| 1814 | - $data = htmlspecialchars($data); | |
| 1955 | + // 1. Decode any entity-encoded HTML so wp_kses sees the real markup. | |
| 1956 | + $data = html_entity_decode( $data, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); | |
| 1957 | + $data = trim( $data ); | |
| 1958 | + $data = stripslashes( $data ); | |
| 1959 | + // 2. Sanitize with a strict allowlist — NOW operating on decoded markup. | |
| 1960 | + $data = wp_kses( $data, wpbot_get_safe_conversation_tags() ); | |
| 1961 | + // 3. Re-encode for safe DB storage; admin.js decodes for rendering. | |
| 1962 | + $data = htmlspecialchars( $data, ENT_QUOTES | ENT_HTML5, 'UTF-8' ); | |
| 1815 | 1963 | return $data; |
| 1816 | 1964 | } |
| 1965 | + | |
| 1966 | +/** | |
| 1967 | + * Returns the strict HTML allowlist for chatbot conversation content. | |
| 1968 | + * | |
| 1969 | + * Critically: no event-handler attributes (onerror, onclick, onload, etc.) are | |
| 1970 | + * allowed — wp_kses strips any attribute not explicitly listed here. | |
| 1971 | + * 'img' is intentionally omitted; bot responses that include images should use | |
| 1972 | + * safe URLs only and can be re-added with only 'src', 'alt', 'class' if needed. | |
| 1973 | + * | |
| 1974 | + * @return array<string, array<string, bool>> | |
| 1975 | + */ | |
| 1976 | +function wpbot_get_safe_conversation_tags() { | |
| 1977 | + return array( | |
| 1978 | + 'ul' => array( 'class' => true ), | |
| 1979 | + 'ol' => array( 'class' => true ), | |
| 1980 | + 'li' => array( 'class' => true, 'id' => true ), | |
| 1981 | + 'div' => array( 'class' => true, 'id' => true ), | |
| 1982 | + 'span' => array( 'class' => true, 'id' => true ), | |
| 1983 | + 'p' => array( 'class' => true ), | |
| 1984 | + 'br' => array(), | |
| 1985 | + 'strong' => array(), | |
| 1986 | + 'em' => array(), | |
| 1987 | + 'b' => array(), | |
| 1988 | + 'i' => array(), | |
| 1989 | + 'a' => array( | |
| 1990 | + 'href' => true, | |
| 1991 | + 'target' => true, | |
| 1992 | + 'rel' => true, | |
| 1993 | + 'class' => true, | |
| 1994 | + ), | |
| 1995 | + // 'img' intentionally excluded — prevents onerror/onload injection. | |
| 1996 | + // Add back with only 'src','alt','class' if bot image responses are needed. | |
| 1997 | + ); | |
| 1998 | +} | |
| 1817 | 1999 | add_action('wp_ajax_qcld_small_talk_import', 'qcld_small_talk_import'); |
| 1818 | 2000 | function qcld_small_talk_import(){ |
| 1819 | 2001 | if ( ! current_user_can( 'manage_options' ) ) { |
| 1820 | 2002 | wp_die(); |
| @@ -1892,4 +2074,7 @@ | ||
| 1892 | 2074 | |
| 1893 | 2075 | } |
| 1894 | 2076 | } |
| 1895 | 2077 | } |
| 2078 | + | |
| 2079 | +// AI Actions Chat Preview | |
| 2080 | + | |