' . $inner . ''; }; return array( 'chat' => array( 'Chat bubble', $wrap( '' ) ), 'message' => array( 'Message', $wrap( '' ) ), 'headset' => array( 'Support headset', $wrap( '' ) ), 'question' => array( 'Question mark', $wrap( '' ) ), 'person' => array( 'Person', $wrap( '' ) ), 'sparkle' => array( 'Sparkle', $wrap( '' ) ), 'heart' => array( 'Heart', $wrap( '' ) ), 'leaf' => array( 'Leaf', $wrap( '' ) ), ); } /** Icon for the chat button: a built-in SVG or the site owner's image. */ public static function launcher_icon() { $icons = self::icons(); $key = (string) Replyvane_Plugin::get( 'icon' ); $url = (string) Replyvane_Plugin::get( 'icon_url' ); if ( 'custom' === $key && $url ) { return array( 'type' => 'img', 'url' => esc_url_raw( $url ), 'fill' => (bool) Replyvane_Plugin::get( 'icon_fill' ), ); } return array( 'type' => 'svg', 'svg' => isset( $icons[ $key ] ) ? $icons[ $key ][1] : $icons['chat'][1], ); } /** * Allowed markup for the plugin's icons, for wp_kses(): the built-in SVG * icons and the site owner's chat button image. */ public static function kses_icons() { $shape = array( 'd' => true, 'cx' => true, 'cy' => true, 'r' => true, 'x' => true, 'y' => true, 'rx' => true, 'width' => true, 'height' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true, ); return array( 'svg' => array_merge( $shape, array( 'viewbox' => true, 'xmlns' => true, 'class' => true, 'aria-hidden' => true, 'focusable' => true, ) ), 'path' => $shape, 'circle' => $shape, 'rect' => $shape, 'img' => array( 'src' => true, 'alt' => true, 'class' => true, ), ); } /** Generic person icon, used for the team before someone joins, or without a profile picture. */ public static function person_svg() { $icons = self::icons(); return $icons['person'][1]; } /* ------------------------------------------------------------------ */ /* Reading the theme */ /* ------------------------------------------------------------------ */ /** * The theme's main colour and body font, and where they came from. * * @return array{accent:?string, font:?string, source:string} */ public static function theme() { static $found = null; if ( null !== $found ) { return $found; } $found = array( 'accent' => null, 'font' => null, 'source' => '', ); // 1. Elementor global colours and fonts (Site Settings). $kit = (int) get_option( 'elementor_active_kit' ); if ( $kit && did_action( 'elementor/loaded' ) ) { $settings = get_post_meta( $kit, '_elementor_page_settings', true ); if ( is_array( $settings ) ) { $colors = array(); foreach ( (array) ( isset( $settings['system_colors'] ) ? $settings['system_colors'] : array() ) as $c ) { if ( ! empty( $c['_id'] ) && ! empty( $c['color'] ) ) { $colors[ $c['_id'] ] = $c['color']; } } foreach ( array( 'accent', 'primary', 'secondary' ) as $id ) { $hex = isset( $colors[ $id ] ) ? self::hex( $colors[ $id ] ) : null; if ( $hex && ! self::near_white_or_black( $hex ) ) { $found['accent'] = $hex; break; } } foreach ( (array) ( isset( $settings['system_typography'] ) ? $settings['system_typography'] : array() ) as $t ) { if ( ! empty( $t['_id'] ) && 'text' === $t['_id'] && ! empty( $t['typography_font_family'] ) ) { $found['font'] = '"' . str_replace( '"', '', $t['typography_font_family'] ) . '", inherit'; } } if ( $found['accent'] || $found['font'] ) { $found['source'] = 'Elementor Site Settings'; } } } // 2. Block themes: the theme's button colour, then its colour palette. if ( ! $found['accent'] && function_exists( 'wp_get_global_settings' ) ) { $palette = wp_get_global_settings( array( 'color', 'palette', 'theme' ) ); if ( is_array( $palette ) ) { $by_slug = array(); foreach ( $palette as $p ) { if ( ! empty( $p['slug'] ) && ! empty( $p['color'] ) ) { $by_slug[ $p['slug'] ] = $p['color']; } } if ( function_exists( 'wp_get_global_styles' ) ) { foreach ( array( array( 'elements', 'button', 'color', 'background' ), array( 'elements', 'link', 'color', 'text' ) ) as $path ) { $value = wp_get_global_styles( $path ); // "var:preset|color|contrast" or "var(--wp--preset--color--contrast)" → the palette colour. if ( is_string( $value ) && preg_match( '/(?:var:preset\|color\||--wp--preset--color--)([a-z0-9\-]+)/', $value, $m ) ) { $value = isset( $by_slug[ $m[1] ] ) ? $by_slug[ $m[1] ] : ''; } $hex = is_string( $value ) ? self::hex( $value ) : null; if ( $hex && self::luminance( $hex ) < 0.92 ) { $found['accent'] = $hex; $found['source'] = 'Theme styles (button colour)'; break; } } } foreach ( array( 'primary', 'accent', 'accent-1', 'secondary', 'contrast-2', 'vivid-cyan-blue' ) as $slug ) { if ( $found['accent'] ) { break; } $hex = isset( $by_slug[ $slug ] ) ? self::hex( $by_slug[ $slug ] ) : null; if ( $hex && ! self::near_white_or_black( $hex ) ) { $found['accent'] = $hex; $found['source'] = $found['source'] ? $found['source'] : 'Theme styles'; break; } } } } if ( ! $found['font'] && function_exists( 'wp_get_global_styles' ) && function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) { $font = wp_get_global_styles( array( 'typography', 'fontFamily' ) ); if ( is_string( $font ) && '' !== $font ) { // Block-theme values like "var:preset|font-family|manrope" become CSS variables. $font = preg_replace( '/^var:preset\|font-family\|(.+)$/', 'var(--wp--preset--font-family--$1)', $font ); $found['font'] = $font . ', inherit'; } } // 3. Classic themes (Customizer settings used by many themes). if ( ! $found['accent'] ) { foreach ( array( 'accent_color', 'primary_color', 'theme_color', 'link_color', 'main_color' ) as $mod ) { $hex = self::hex( (string) get_theme_mod( $mod, '' ) ); if ( $hex && ! self::near_white_or_black( $hex ) ) { $found['accent'] = $hex; $found['source'] = 'Theme Customizer'; break; } } } return $found; } /* ------------------------------------------------------------------ */ /* Colour maths */ /* ------------------------------------------------------------------ */ public static function hex( $value ) { $value = trim( (string) $value ); if ( preg_match( '/^#([0-9a-f]{3})$/i', $value, $m ) ) { return '#' . strtolower( $m[1][0] . $m[1][0] . $m[1][1] . $m[1][1] . $m[1][2] . $m[1][2] ); } if ( preg_match( '/^#([0-9a-f]{6})([0-9a-f]{2})?$/i', $value, $m ) ) { return '#' . strtolower( $m[1] ); } if ( preg_match( '/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i', $value, $m ) ) { return sprintf( '#%02x%02x%02x', min( 255, $m[1] ), min( 255, $m[2] ), min( 255, $m[3] ) ); } return null; } private static function rgb( $hex ) { $hex = ltrim( $hex, '#' ); return array( hexdec( substr( $hex, 0, 2 ) ), hexdec( substr( $hex, 2, 2 ) ), hexdec( substr( $hex, 4, 2 ) ) ); } /** Mixes a colour with another; $amount 0 = first colour, 1 = second. */ public static function mix( $hex, $with, $amount ) { $a = self::rgb( $hex ); $b = self::rgb( $with ); $c = array(); for ( $i = 0; $i < 3; $i++ ) { $c[] = (int) round( $a[ $i ] + ( $b[ $i ] - $a[ $i ] ) * $amount ); } return sprintf( '#%02x%02x%02x', $c[0], $c[1], $c[2] ); } public static function luminance( $hex ) { $l = array(); foreach ( self::rgb( $hex ) as $v ) { $v = $v / 255; $l[] = $v <= 0.03928 ? $v / 12.92 : pow( ( $v + 0.055 ) / 1.055, 2.4 ); } return 0.2126 * $l[0] + 0.7152 * $l[1] + 0.0722 * $l[2]; } public static function contrast( $a, $b ) { $la = self::luminance( $a ); $lb = self::luminance( $b ); return ( max( $la, $lb ) + 0.05 ) / ( min( $la, $lb ) + 0.05 ); } /** White or near-black text, whichever is easier to read on this colour. */ public static function readable_on( $hex ) { return self::contrast( $hex, '#ffffff' ) >= self::contrast( $hex, '#172a2c' ) ? '#ffffff' : '#172a2c'; } /** Darkens a colour until it can be read as text on white (WCAG AA 4.5:1). */ private static function text_safe( $hex ) { $out = $hex; for ( $i = 1; $i <= 10 && self::contrast( $out, '#ffffff' ) < 4.5; $i++ ) { $out = self::mix( $hex, '#000000', $i * 0.08 ); } return $out; } private static function near_white_or_black( $hex ) { $l = self::luminance( $hex ); return $l > 0.92 || $l < 0.008; } /* ------------------------------------------------------------------ */ /* The final colours sent to the chat window */ /* ------------------------------------------------------------------ */ public static function accent() { if ( 'custom' === Replyvane_Plugin::get( 'color_mode' ) ) { $hex = self::hex( (string) Replyvane_Plugin::get( 'color' ) ); if ( $hex ) { return $hex; } } $theme = self::theme(); return $theme['accent'] ? $theme['accent'] : self::FALLBACK; } public static function font() { if ( 'custom' === Replyvane_Plugin::get( 'font_mode' ) ) { $font = trim( (string) Replyvane_Plugin::get( 'font_custom' ) ); if ( '' !== $font ) { return preg_replace( '/[^\w\s,"\'\-]/', '', $font ) . ', inherit'; } } $theme = self::theme(); return $theme['font'] ? $theme['font'] : 'inherit'; } /** CSS variables for the chat window. */ public static function css_vars() { $accent = self::accent(); $q_bg = self::hex( (string) Replyvane_Plugin::get( 'question_bg' ) ); $q_txt = self::hex( (string) Replyvane_Plugin::get( 'question_text' ) ); $a_bg = self::hex( (string) Replyvane_Plugin::get( 'answer_bg' ) ); $q_bg = $q_bg ? $q_bg : self::mix( $accent, '#ffffff', 0.93 ); $a_bg = $a_bg ? $a_bg : self::mix( $accent, '#ffffff', 0.9 ); return array( '--replyvane-accent' => $accent, '--replyvane-on-accent' => self::readable_on( $accent ), '--replyvane-accent-text' => self::text_safe( $accent ), '--replyvane-mist' => $a_bg, '--replyvane-a-text' => self::readable_on( $a_bg ), '--replyvane-line' => self::mix( $accent, '#ffffff', 0.78 ), '--replyvane-q-bg' => $q_bg, '--replyvane-q-text' => $q_txt ? $q_txt : self::readable_on( $q_bg ), '--replyvane-q-hover' => self::mix( $q_bg, $accent, 0.14 ), '--replyvane-font' => self::font(), ); } }