0 ? '∞' : '-∞'; } $s = number_format( abs( $n ), $max_frac, '.', ',' ); if ( $max_frac > 0 ) { list( $int, $frac ) = explode( '.', $s ); $frac = rtrim( $frac, '0' ); if ( strlen( $frac ) < $min_frac ) { $frac = str_pad( $frac, $min_frac, '0' ); } $s = '' === $frac ? $int : $int . '.' . $frac; } return ( $n < 0 ? '-' : '' ) . $s; } /** Number.isInteger(x) */ protected function choice2_is_integer( $n ) { if ( is_int( $n ) ) { return true; } return is_float( $n ) && is_finite( $n ) && floor( $n ) == $n; } /** Shared field visibility test used by every renderer in this group. */ protected function choice2_hidden( $field ) { $vis = WPVFB_JS::get( $field, 'visibility' ); return 'hidden' === $vis || 'administrative' === $vis || ( WPVFB_JS::truthy( WPVFB_JS::get( $field, 'enableConditionalLogic' ) ) && ! WPVFB_JS::truthy( WPVFB_JS::get( $field, 'conditionalLogicMet' ) ) ); } /** * Consent link URL allow-list (security deviation from route.ts 6917-6919): * only http(s), mailto, tel and relative URLs become href. */ protected function choice2_safe_link_url( $url ) { $probe = preg_replace( '/[\x00-\x20\x7f]+/', '', (string) $url ); if ( preg_match( '/^(https?:|mailto:|tel:)/i', $probe ) ) { return true; } // Relative URL: no scheme before the first "/", "?" or "#". return ! preg_match( '/^[^\/?#]*:/', $probe ); } // ------------------------------------------------------------------ // Renderers // ------------------------------------------------------------------ /** renderRadioField — route.ts lines 6449-6692 */ public function render_radio_field( $field, $gs ) { $J = 'WPVFB_JS'; $f = function ( $p ) use ( $field ) { return WPVFB_JS::get( $field, $p ); }; $g = function ( $p ) use ( $gs ) { return WPVFB_JS::get( $gs, $p ); }; $visibility_style = $this->choice2_hidden( $field ) ? 'display: none;' : ''; $custom_class = $J::truthy( $f( 'cssClassName' ) ) ? ' ' . $J::str( $f( 'cssClassName' ) ) : ''; $label_class = 'left' === $g( 'labelPlacement' ) ? 'flex items-start space-x-4' : 'flex flex-col'; $options = $f( 'options' ); if ( $J::is_array( $options ) && count( $options ) > 0 ) { $radio_options = array(); foreach ( $options as $index => $option ) { $o = function ( $p ) use ( $option ) { return WPVFB_JS::get( $option, $p ); }; $radio_options[] = array( 'value' => $J::or_( $o( 'value' ), 'option_' . ( $index + 1 ) ), 'label' => $J::or_( $o( 'label' ), 'Option ' . ( $index + 1 ) ), 'price' => $J::defined( $option, 'price' ) ? $o( 'price' ) : 0, 'points' => $J::defined( $option, 'points' ) ? $o( 'points' ) : 0, 'badge' => $o( 'badge' ), 'description' => $o( 'description' ), 'labelFontWeight' => $o( 'labelFontWeight' ), 'labelFontStyle' => $o( 'labelFontStyle' ), 'labelTextColor' => $o( 'labelTextColor' ), 'fontSize' => $o( 'fontSize' ), 'lineHeight' => $o( 'lineHeight' ), ); } } else { $u = $J::undef(); $radio_options = array( array( 'value' => 'option_1', 'label' => 'Option 1', 'price' => 0, 'points' => 0, 'badge' => $u, 'description' => $u, 'labelFontWeight' => $u, 'labelFontStyle' => $u, 'labelTextColor' => $u, 'fontSize' => $u, 'lineHeight' => $u ) ); } $default_value = is_string( $f( 'defaultValue' ) ) ? $f( 'defaultValue' ) : ''; $radio_selected_color = $J::or_( $f( 'radioSelectedColor' ), $g( 'checkboxSelectedColor' ), $g( 'globalColorScheme.checkboxSelectedColor' ), '#2563EB' ); $rc = $this->a( $radio_selected_color ); $options_html = ''; foreach ( $radio_options as $index => $opt ) { $is_selected = $J::eq( $default_value, $opt['value'] ); $option_text_color = ! $J::is_nullish( $opt['labelTextColor'] ) ? $opt['labelTextColor'] : $J::or_( $f( 'textColor' ), $g( 'inputTextColor' ), $g( 'primaryTextColor' ) ); $option_font_size_value = ! $J::is_nullish( $opt['fontSize'] ) ? $opt['fontSize'] : ( ! $J::is_nullish( $f( 'fontSize' ) ) ? $f( 'fontSize' ) : $g( 'fontSize' ) ); $option_font_size = ( is_int( $option_font_size_value ) || is_float( $option_font_size_value ) ) ? $J::str( $option_font_size_value ) . 'px' : $J::or_( $option_font_size_value, $J::str( $g( 'fontSize' ) ) . 'px' ); $option_line_height_value = ! $J::is_nullish( $opt['lineHeight'] ) ? $opt['lineHeight'] : ( ! $J::is_nullish( $f( 'lineHeight' ) ) ? $f( 'lineHeight' ) : $g( 'lineHeight' ) ); $option_line_height = ( is_int( $option_line_height_value ) || is_float( $option_line_height_value ) ) ? $J::str( $option_line_height_value ) . 'px' : $J::or_( $option_line_height_value, $J::str( $g( 'lineHeight' ) ) . 'px' ); $option_font_weight = ! $J::is_nullish( $opt['labelFontWeight'] ) ? $opt['labelFontWeight'] : ( $J::defined( $field, 'labelFontWeight' ) ? $f( 'labelFontWeight' ) : $J::or_( $g( 'labelFontWeight' ), 400 ) ); $option_font_style = $J::or_( $opt['labelFontStyle'], $f( 'labelFontStyle' ), 'normal' ); $input_id = 'radio-' . $J::str( $f( 'id' ) ) . '-' . $index; $data_price = $J::is_undef( $opt['price'] ) ? 0 : $opt['price']; $data_points = $J::is_undef( $opt['points'] ) ? 0 : $opt['points']; $required_attr = $J::truthy( $f( 'required' ) ) ? 'data-required="true"' : ''; if ( 'card' === $f( 'radioStyle' ) ) { $clean_title = preg_replace( '/\s*[—–-]\s*\$[\d,.]+$/uD', '', $J::str( $opt['label'] ) ); $option_font_size_px = ( is_int( $option_font_size_value ) || is_float( $option_font_size_value ) ) ? $option_font_size_value : $J::parse_float( $J::str( $J::or_( $option_font_size_value, $g( 'fontSize' ), 14 ) ) ); $has_price = ! $J::is_nullish( $opt['price'] ); if ( 'vertical-center' === $f( 'radioCardLayout' ) ) { $paint_kind = 'radio-card-circle'; $options_html .= '
' . ( $J::truthy( $opt['badge'] ) ? '' . $this->t( $opt['badge'] ) . '' : '' ) . '
' . $this->t( $clean_title ) . '
' . ( $has_price ? '
$' . $this->t( $this->choice2_locale( $opt['price'] ) ) . '
' : '' ) . '
' . ( $is_selected ? '
' : '' ) . '
'; continue; } $paint_kind = 'radio-card'; $price_html = ''; if ( $has_price ) { $price_num = $J::to_number( $opt['price'] ); $price_text = $this->choice2_is_integer( $price_num ) ? '$' . $this->choice2_locale( $price_num ) : '$' . $this->choice2_locale( $price_num, 2, 2 ); $price_px = ( is_int( $option_font_size_px ) || is_float( $option_font_size_px ) ) ? $option_font_size_px + 2 : NAN; $price_html = '' . $this->t( $price_text ) . ''; } $options_html .= '
' . $this->t( $clean_title ) . '
' . $price_html . '
'; continue; } $options_html .= '
'; } $grid_styles = ' display: grid; grid-template-columns: repeat(' . $J::str( $J::or_( $f( 'radioRowColumns' ), '1' ) ) . ', minmax(0, 1fr)); gap: 8px; width: ' . $J::str( $J::or_( $f( 'width' ), '100%' ) ) . '; '; $radio_responsive_styles = ' @media (max-width: 768px) { .radio-group { grid-template-columns: 1fr !important; /* Stack all options vertically on mobile */ gap: 8px !important; } } '; $margin = $J::or_( $f( 'margin' ), array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ) ); $m = function ( $k ) use ( $margin ) { return WPVFB_JS::get( $margin, $k ); }; $m_bottom = $J::defined( $margin, 'bottom' ) ? $m( 'bottom' ) : 15; $radio_label_font_weight = $J::defined( $field, 'labelFontWeight' ) ? $f( 'labelFontWeight' ) : $J::or_( $g( 'labelFontWeight' ), 600 ); $radio_label_letter_spacing = $J::defined( $field, 'labelLetterSpacing' ) ? $f( 'labelLetterSpacing' ) : $g( 'letterSpacing' ); $label_helper_text = $f( 'labelHelperText' ); $label_helper_color = $J::or_( $f( 'labelHelperColor' ), $radio_selected_color ); $label_line_height = $J::str( $J::or_( $g( 'labelLineHeight' ), $g( 'lineHeight' ) ) ) . 'px'; $left = 'left' === $g( 'labelPlacement' ); $label_html = ''; if ( 'none' !== $g( 'labelPlacement' ) && 'hidden' !== $f( 'labelVisibility' ) ) { $lm_bottom = $J::defined( $field, 'labelMargin.bottom' ) ? $f( 'labelMargin.bottom' ) : 10; $label_html = '
' . ( $J::truthy( $label_helper_text ) ? '' . $this->t( $label_helper_text ) . '' : '' ) . '
'; } $desc = function ( $where ) use ( $J, $f, $g, $label_line_height ) { if ( $where !== $g( 'descriptionPlacement' ) || ! $J::truthy( $f( 'description' ) ) ) { return ''; } return '

' . $this->t( $f( 'description' ) ) . '

'; }; return ' ' . $this->add_inline_css( $radio_responsive_styles ) . '
' . $label_html . '
' . $desc( 'above' ) . '
' . $options_html . '
' . $desc( 'below' ) . '
'; } /** renderConsentField — route.ts lines 6886-6927 */ public function render_consent_field( $field, $gs ) { $J = 'WPVFB_JS'; $f = function ( $p ) use ( $field ) { return WPVFB_JS::get( $field, $p ); }; $g = function ( $p ) use ( $gs ) { return WPVFB_JS::get( $gs, $p ); }; $visibility_style = $this->choice2_hidden( $field ) ? 'display: none;' : ''; $custom_class = $J::truthy( $f( 'cssClassName' ) ) ? ' ' . $J::str( $f( 'cssClassName' ) ) : ''; $is_checked = $J::truthy( $f( 'defaultValue' ) ); $accent_color = $J::or_( $f( 'selectedBorderColor' ), $g( 'checkboxSelectedColor' ), $g( 'globalColorScheme.checkboxSelectedColor' ), '#2563EB' ); $consent_text = $J::str( $J::or_( $f( 'consentText' ), 'I agree to the [Terms of Service](/pages/terms) and [Privacy Policy](/pages/privacy)' ) ); $target_attr = false !== $f( 'openInNewTab' ) ? ' target="_blank" rel="noopener noreferrer"' : ''; // Security deviation: only http(s)/mailto/tel/relative URLs become links; others render as plain text. $parsed_text = preg_replace_callback( '/\[(.*?)\]\((.*?)\)/', function ( $mm ) use ( $target_attr, $accent_color ) { if ( ! $this->choice2_safe_link_url( $mm[2] ) ) { return $mm[1]; } return '' . $mm[1] . ''; }, $consent_text ); $margin = $J::or_( $f( 'margin' ), array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ) ); $m = function ( $k ) use ( $margin ) { return WPVFB_JS::get( $margin, $k ); }; $mb = $m( 'bottom' ); $m_bottom = ( ! $J::is_undef( $mb ) && ! ( ( is_int( $mb ) || is_float( $mb ) ) && 0 == $mb ) ) ? $mb : 24; $required = false !== $f( 'required' ); return ' '; } }