this->get_selector_vars_css( $selector, $vars, $form_id ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Output CSS vars for the form added as a shortcode. * * @since 1.9.7 * * @param array $atts Shortcode attributes. */ public function output_css_vars_for_shortcode( array $atts ): void { if ( empty( $atts['id'] ) ) { return; } $form_handler = wpforms()->obj( 'form' ); if ( ! $form_handler ) { return; } $form_id = (int) $atts['id']; $form_data = $form_handler->get( $form_id, [ 'content_only' => true ] ); if ( empty( $form_data ) ) { return; } $attr = isset( $form_data['settings']['themes'] ) ? (array) $form_data['settings']['themes'] : []; $attr = $this->maybe_override_attributes( $attr ); $css_vars = $this->get_customized_css_vars( $attr ); $css_vars = $this->add_css_vars_units( $css_vars ); $selector = "#wpforms-{$form_id}"; $style_id = "wpforms-css-vars-{$form_id}"; $this->output_selector_vars( $selector, $css_vars, $style_id, $form_id ); $this->output_custom_css( $attr, $selector, $style_id ); } /** * Output custom CSS. * * @since 1.9.7 * * @param array $attr Attributes. * @param string $selector Selector. * @param string $style_id Style ID. * * @noinspection PhpMissingParamTypeInspection */ private function output_custom_css( array $attr, string $selector, string $style_id ): void { if ( wpforms_get_render_engine() === 'classic' ) { return; } $custom_css = trim( $attr['customCss'] ?? '' ); if ( empty( $custom_css ) ) { return; } printf( '', sanitize_key( $style_id ), $selector, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped wp_strip_all_tags( $custom_css ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } /** * Maybe override attributes with themes.json settings. * * @since 1.9.7 * * @param array $attr Attributes. * * @return array */ private function maybe_override_attributes( array $attr ): array { $theme_slug = (string) ( $attr['wpformsTheme'] ?? '' ); if ( empty( $theme_slug ) ) { return $attr; } $attr = $this->normalize_background_url( $attr ); $theme_data = $this->get_themes_data_object()->get_theme( $theme_slug ); $settings = $theme_data['settings'] ?? []; return array_merge( $attr, $settings ); } /** * Normalize background URL. * * Check if the background URL is not wrapped in url() and add it if needed. * * @since 1.9.7 * * @param array $attr Attributes. */ private function normalize_background_url( array $attr ): array { if ( ! isset( $attr['backgroundUrl'] ) ) { return $attr; } if ( strpos( $attr['backgroundUrl'], 'url(' ) === 0 ) { return $attr; } $attr['backgroundUrl'] = 'url(' . $attr['backgroundUrl'] . ')'; return $attr; } /** * Get themes data object. * * @since 1.9.7 * * @return ThemesData */ private function get_themes_data_object(): ThemesData { if ( wpforms()->is_pro() ) { return new ProThemesData( new StockPhotos() ); } return new LiteThemesData(); } /** * Add CSS vars units. * * Form builder saves values without pixels, we need to add them before outputting as CSS vars. * * @since 1.9.7 * * @param array $css_vars CSS vars. * * @return array */ private function add_css_vars_units( array $css_vars ): array { $has_pixels = [ 'field-border-size', 'field-border-radius', 'button-border-size', 'button-border-radius', 'container-padding', 'container-border-width', 'container-border-radius', ]; foreach ( $has_pixels as $key ) { if ( isset( $css_vars[ $key ] ) && is_numeric( $css_vars[ $key ] ) && $css_vars[ $key ] > 0 ) { $css_vars[ $key ] .= 'px'; } } return $css_vars; } /** * Get selector variables CSS. * * @since 1.9.3 * * @param string $selector Selector. * @param array $vars Variables data. * @param string|int $form_id Form ID. Optional. Default is an empty string. * * @return string */ private function get_selector_vars_css( string $selector, array $vars, $form_id = '' ): string { return sprintf( '%1$s { %2$s }', $selector, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped wp_strip_all_tags( $this->get_vars_css( $vars, $form_id ) ) ); } /** * Pre print vars filter. * * @since 1.8.8 * * @param array $vars Variables data. * @param string|int $form_id Form ID. Optional. Default is an empty string. * * @return array */ private function get_pre_print_vars( array $vars, $form_id = '' ): array { // Normalize the `background-url` variable. if ( isset( $vars['background-url'] ) ) { $vars['background-url'] = $vars['background-url'] === 'url()' ? 'none' : $vars['background-url']; } /** * Filter CSS variables right before printing the CSS. * * @since 1.8.8 * * @param array $vars CSS variables. * @param int $form_id Form ID. Optional. Default is an empty string. */ return (array) apply_filters( 'wpforms_frontend_css_vars_pre_print_filter', $vars, $form_id ); } /** * Generate CSS code from given vars data. * * @since 1.8.1 * * @param array $vars Variables data. * @param string|int $form_id Form ID. Optional. Default is an empty string. */ private function get_vars_css( array $vars, $form_id = '' ): string { $vars = $this->get_pre_print_vars( $vars, $form_id ); $result = ''; foreach ( $vars as $name => $value ) { if ( ! is_string( $value ) ) { continue; } if ( $value === '0' ) { $value = '0px'; } $result .= "--wpforms-{$name}: {$value};\n"; if ( in_array( $name, self::SPARE_VARS, true ) ) { $result .= "--wpforms-{$name}-spare: {$value};\n"; } } return $result; } /** * Get customized CSS vars. * * @since 1.8.3 * * @param array $attr Attributes passed by integration. * * @return array */ public function get_customized_css_vars( array $attr ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh $root_css_vars = $this->get_vars(); $css_vars = []; foreach ( $attr as $key => $value ) { $var_name = strtolower( preg_replace( '/[A-Z]/', '-$0', $key ) ); // Skip an attribute that is not the CSS var or has the default value. if ( empty( $root_css_vars[ $var_name ] ) || $root_css_vars[ $var_name ] === $value ) { continue; } $css_vars[ $var_name ] = $value; } // Reset border size in case of border style is `none`. $css_vars = $this->maybe_reset_border( $css_vars, 'field-border' ); $css_vars = $this->maybe_reset_border( $css_vars, 'button-border' ); // Set the button alternative background color and use border color for accent in case of transparent color. $button_bg_color = $css_vars['button-background-color'] ?? $root_css_vars['button-background-color']; if ( $this->is_transparent_color( $button_bg_color ) ) { $css_vars['button-background-color-alt'] = $button_bg_color; $border_color = $css_vars['button-border-color'] ?? $root_css_vars['button-border-color']; $css_vars['button-background-color'] = $this->is_transparent_color( $border_color ) ? $root_css_vars['button-background-color'] : $border_color; $button_bg_color = $css_vars['button-background-color']; } $button_bg_color = strtolower( $button_bg_color ); // Set the button alternative text color in case if the background and text color are identical. $button_text_color = strtolower( $css_vars['button-text-color'] ?? $root_css_vars['button-text-color'] ); if ( $button_bg_color === $button_text_color || $this->is_transparent_color( $button_text_color ) ) { $css_vars['button-text-color-alt'] = $this->get_contrast_color( $button_bg_color ); } $size_css_vars = $this->get_size_css_vars( $attr ); return array_merge( $css_vars, $size_css_vars ); } /** * Reset border size in case of border style is `none`. * * @since 1.9.7 * * @param array $css_vars CSS vars. * @param string $key Key. * * @return array */ private function maybe_reset_border( array $css_vars, string $key ): array { $style_key = $key . '-style'; $size_key = $key . '-size'; if ( isset( $css_vars[ $style_key ] ) && $css_vars[ $style_key ] === 'none' ) { $css_vars[ $size_key ] = '0px'; } return $css_vars; } /** * Checks if the provided color has transparency. * * @since 1.8.8 * * @param string $color The color to check. * * @return bool */ private function is_transparent_color( string $color ): bool { $rgba = $this->get_color_as_rgb_array( $color ); $opacity_threshold = 0.33; $opacity = $rgba[3] ?? 1; return $opacity < $opacity_threshold; } /** * Get contrast color relative to a given color. * * @since 1.8.8 * * @param string|array $color The color. * * @return string */ private function get_contrast_color( $color ): string { $rgba = is_array( $color ) ? $color : $this->get_color_as_rgb_array( $color ); $avg = (int) ( ( ( array_sum( $rgba ) ) / 3 ) * ( $rgba[3] ?? 1 ) ); return $avg < 128 ? '#ffffff' : '#000000'; } /** * Get size CSS vars. * * @since 1.8.3 * @since 1.8.8 Removed $css_vars argument. * * @param array $attr Attributes passed by integration. * * @return array */ private function get_size_css_vars( array $attr ): array { $size_items = [ 'field', 'label', 'button', 'container-shadow' ]; $size_css_vars = []; foreach ( $size_items as $item ) { $item_attr = preg_replace_callback( '/-(\w)/', static function ( $matches ) { return strtoupper( $matches[1] ); }, $item ); $item_attr .= 'Size'; $item_key = $item . '-size'; $item_constant = 'self::' . str_replace( '-', '_', strtoupper( $item ) ) . '_SIZE'; if ( empty( $attr[ $item_attr ] ) ) { continue; } $size_css_vars[] = $this->get_complex_vars( $item_key, constant( $item_constant )[ $attr[ $item_attr ] ] ); } return empty( $size_css_vars ) ? [] : array_merge( ...$size_css_vars ); } /** * Get color as an array of RGB(A) values. * * @since 1.8.8 * * @param string $color Color. * * @return array|bool Color as an array of RGBA values. False on error. */ private function get_color_as_rgb_array( string $color ) { // Remove # from the beginning of the string and remove whitespaces. $color = preg_replace( '/^#/', '', strtolower( trim( $color ) ) ); $color = str_replace( ' ', '', (string) $color ); if ( $color === 'transparent' ) { $color = 'rgba(0,0,0,0)'; } $rgba = $color; $rgb_array = []; // Check if color is in HEX(A) format. $is_hex = preg_match( '/[0-9a-f]{6,8}$/', $rgba ); if ( $is_hex ) { // Search and split HEX(A) color into an array of char couples. preg_match_all( '/\w\w/', $rgba, $rgb_array ); $rgb_array = array_map( static function ( $value ) { return hexdec( '0x' . $value ); }, $rgb_array[0] ?? [] ); $rgb_array[3] = ( $rgb_array[3] ?? 255 ) / 255; } else { $rgba = preg_replace( '/[^\d,.]/', '', $rgba ); $rgb_array = explode( ',', $rgba ); } return $rgb_array; } }