' . $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(),
);
}
}