*/ public static function defaults(): array { return array( 'preset' => 'default', 'accent' => '', // A #rrggbb colour, or '' for none. 'labels' => 'above', 'button' => 'theme', // Overrides of the theme-based values ('' = take it from the theme). 'radius' => '', // Corner radius in px, 0 to RADIUS_MAX. 'gap' => '', // Space between rows in px, 0 to GAP_MAX. 'border' => '', // Field border colour, #rrggbb. 'background' => '', // Field background colour, #rrggbb. ); } /** * A form's style settings, with defaults for anything not stored. * * @param int $form_id Form post ID. * @return array */ public static function get( int $form_id ): array { $stored = $form_id > 0 ? get_post_meta( $form_id, self::META, true ) : array(); return self::sanitize( is_array( $stored ) ? $stored : array() ); } /** * Sanitizes style settings (also the registered meta sanitize callback). * Unknown choices fall back to the default; unknown keys (such as the * custom CSS that versions before 1.0.0 stored) are dropped. * * @param mixed $value Raw settings. * @return array */ public static function sanitize( mixed $value ): array { $value = is_array( $value ) ? $value : array(); $choose = static function ( string $key, array $choices ) use ( $value ): string { $choice = is_string( $value[ $key ] ?? null ) ? $value[ $key ] : ''; return in_array( $choice, $choices, true ) ? $choice : $choices[0]; }; $colour = static function ( string $key ) use ( $value ): string { $hex = is_string( $value[ $key ] ?? null ) ? strtolower( trim( $value[ $key ] ) ) : ''; return 1 === preg_match( '/^#[0-9a-f]{6}$/', $hex ) ? $hex : ''; }; $pixels = static function ( string $key, int $max ) use ( $value ): string { $number = is_scalar( $value[ $key ] ?? null ) ? trim( (string) $value[ $key ] ) : ''; return 1 === preg_match( '/^\d{1,3}$/', $number ) && (int) $number <= $max ? (string) (int) $number : ''; }; return array( 'preset' => $choose( 'preset', self::PRESETS ), 'accent' => $colour( 'accent' ), 'labels' => $choose( 'labels', self::LABELS ), 'button' => $choose( 'button', self::BUTTONS ), 'radius' => $pixels( 'radius', self::RADIUS_MAX ), 'gap' => $pixels( 'gap', self::GAP_MAX ), 'border' => $colour( 'border' ), 'background' => $colour( 'background' ), ); } /** * Settings posted from the Style tab, sanitized. * * @param mixed $posted Raw posted settings. * @return array */ public static function from_request( mixed $posted ): array { $posted = is_array( $posted ) ? $posted : array(); // A colour input always sends a value; its checkbox says whether to use it. foreach ( array( 'accent', 'border', 'background' ) as $key ) { if ( empty( $posted[ $key . '_on' ] ) ) { $posted[ $key ] = ''; } } return self::sanitize( $posted ); } /** * CSS classes for the form element. * * @param int $form_id Form post ID. * @param array $style Sanitized settings. * @return string[] */ public static function classes( int $form_id, array $style ): array { $classes = array( 'tflexforms-form-' . $form_id, 'tflexforms-style-' . $style['preset'], 'tflexforms-labels-' . $style['labels'], 'tflexforms-button-' . $style['button'], ); if ( '' !== $style['accent'] ) { $classes[] = 'tflexforms-has-accent'; } return $classes; } /** * Extra classes for the submit button. With the Theme preset and the * theme button, the button also gets WordPress's element class, so it * looks like the buttons set in the theme's Global Styles. * * @param array $style Sanitized settings. * @return string[] */ public static function button_classes( array $style ): array { return 'theme' === $style['preset'] && 'theme' === $style['button'] ? array( 'wp-element-button' ) : array(); } /** * Inline custom properties for the form element ('' when none): the * accent with a text colour that stays readable on it, and each override * of a theme-based value. They win over the theme's values because they * sit on the form itself. * * @param array $style Sanitized settings. */ public static function inline_style( array $style ): string { $properties = array(); if ( '' !== $style['accent'] ) { $properties['accent'] = $style['accent']; $properties['accent-text'] = self::text_on( $style['accent'] ); } if ( '' !== ( $style['radius'] ?? '' ) ) { $properties['radius'] = $style['radius'] . 'px'; } if ( '' !== ( $style['gap'] ?? '' ) ) { $properties['gap'] = $style['gap'] . 'px'; } if ( '' !== ( $style['border'] ?? '' ) ) { $properties['border-color'] = $style['border']; } if ( '' !== ( $style['background'] ?? '' ) ) { $properties['field-background'] = $style['background']; } $css = array(); foreach ( $properties as $name => $property_value ) { $css[] = '--tflexforms-' . $name . ':' . $property_value; } return implode( ';', $css ); } /** * Black or white, whichever contrasts more with a #rrggbb colour * (WCAG relative luminance). * * @param string $hex Colour. */ public static function text_on( string $hex ): string { $channel = static function ( string $pair ): float { $c = hexdec( $pair ) / 255; return $c <= 0.03928 ? $c / 12.92 : ( ( $c + 0.055 ) / 1.055 ) ** 2.4; }; $luminance = 0.2126 * $channel( substr( $hex, 1, 2 ) ) + 0.7152 * $channel( substr( $hex, 3, 2 ) ) + 0.0722 * $channel( substr( $hex, 5, 2 ) ); // Contrast with white is 1.05 / (L + 0.05); with black (L + 0.05) / 0.05. return 1.05 / ( $luminance + 0.05 ) >= ( $luminance + 0.05 ) / 0.05 ? '#ffffff' : '#000000'; } }