array( 'label' => __( 'Form', '2ranq-forms' ), 'settings' => array( array( 'key' => 'form_bg', 'type' => 'color', 'var' => 'form-bg', 'label' => __( 'Background', '2ranq-forms' ), 'default' => 'transparent' ), array( 'key' => 'form_bg_image', 'type' => 'image', 'var' => 'form-bg-image', 'label' => __( 'Background image', '2ranq-forms' ), 'default' => '' ), array( 'key' => 'form_padding', 'type' => 'size', 'var' => 'form-padding', 'label' => __( 'Inner spacing', '2ranq-forms' ), 'default' => '0', 'min' => 0, 'max' => 80 ), array( 'key' => 'form_border_width', 'type' => 'size', 'var' => 'form-border-width', 'label' => __( 'Border width', '2ranq-forms' ), 'default' => '0', 'min' => 0, 'max' => 12 ), array( 'key' => 'form_border_color', 'type' => 'color', 'var' => 'form-border-color', 'label' => __( 'Border colour', '2ranq-forms' ), 'default' => '#e3e5ea' ), array( 'key' => 'form_radius', 'type' => 'size', 'var' => 'form-radius', 'label' => __( 'Corner radius', '2ranq-forms' ), 'default' => '0', 'min' => 0, 'max' => 40 ), array( 'key' => 'form_shadow', 'type' => 'select', 'var' => 'form-shadow', 'label' => __( 'Shadow', '2ranq-forms' ), 'default' => 'none', 'options' => array( 'none' => array( __( 'None', '2ranq-forms' ), 'none' ), 'soft' => array( __( 'Soft', '2ranq-forms' ), '0 1px 3px rgba(0,0,0,.08)' ), 'medium' => array( __( 'Medium', '2ranq-forms' ), '0 4px 14px rgba(0,0,0,.10)' ), 'strong' => array( __( 'Strong', '2ranq-forms' ), '0 10px 30px rgba(0,0,0,.16)' ), ), ), array( 'key' => 'form_max_width', 'type' => 'size', 'var' => 'form-max-width', 'label' => __( 'Maximum width', '2ranq-forms' ), 'default' => '640', 'min' => 240, 'max' => 1200 ), array( 'key' => 'field_gap', 'type' => 'size', 'var' => 'field-gap', 'label' => __( 'Space between fields', '2ranq-forms' ), 'default' => '16', 'min' => 0, 'max' => 64 ), ), ), 'fields' => array( 'label' => __( 'Fields', '2ranq-forms' ), 'settings' => array( array( 'key' => 'field_bg', 'type' => 'color', 'var' => 'field-bg', 'label' => __( 'Background', '2ranq-forms' ), 'default' => '#ffffff' ), array( 'key' => 'field_text', 'type' => 'color', 'var' => 'field-text', 'label' => __( 'Text colour', '2ranq-forms' ), 'default' => '#16181d' ), array( 'key' => 'field_placeholder', 'type' => 'color', 'var' => 'field-placeholder', 'label' => __( 'Placeholder colour', '2ranq-forms' ), 'default' => '#8c92a0' ), array( 'key' => 'field_height', 'type' => 'size', 'var' => 'field-height', 'label' => __( 'Height', '2ranq-forms' ), 'default' => '44', 'min' => 28, 'max' => 80 ), array( 'key' => 'field_padding', 'type' => 'size', 'var' => 'field-padding', 'label' => __( 'Inner spacing', '2ranq-forms' ), 'default' => '12', 'min' => 0, 'max' => 32 ), array( 'key' => 'field_font_size', 'type' => 'size', 'var' => 'field-font-size', 'label' => __( 'Text size', '2ranq-forms' ), 'default' => '16', 'min' => 11, 'max' => 28 ), array( 'key' => 'field_border_width', 'type' => 'size', 'var' => 'field-border-width', 'label' => __( 'Border width', '2ranq-forms' ), 'default' => '1', 'min' => 0, 'max' => 8 ), array( 'key' => 'field_border_color', 'type' => 'color', 'var' => 'field-border-color', 'label' => __( 'Border colour', '2ranq-forms' ), 'default' => '#c9ccd4' ), array( 'key' => 'field_border_style', 'type' => 'select', 'var' => 'field-border-style', 'label' => __( 'Border style', '2ranq-forms' ), 'default' => 'solid', 'options' => array( 'solid' => array( __( 'Solid', '2ranq-forms' ), 'solid' ), 'dashed' => array( __( 'Dashed', '2ranq-forms' ), 'dashed' ), 'dotted' => array( __( 'Dotted', '2ranq-forms' ), 'dotted' ), 'none' => array( __( 'None', '2ranq-forms' ), 'none' ), ), ), array( 'key' => 'field_shape', 'type' => 'select', 'var' => 'field-shape', 'label' => __( 'Field shape', '2ranq-forms' ), 'default' => 'box', 'options' => array( 'box' => array( __( 'Full border', '2ranq-forms' ), 'var(--ranq-field-border-width)' ), 'underline' => array( __( 'Bottom line only', '2ranq-forms' ), '0 0 var(--ranq-field-border-width) 0' ), ), ), array( 'key' => 'field_radius', 'type' => 'size', 'var' => 'field-radius', 'label' => __( 'Corner radius', '2ranq-forms' ), 'default' => '6', 'min' => 0, 'max' => 40 ), array( 'key' => 'field_focus_color', 'type' => 'color', 'var' => 'field-focus-color', 'label' => __( 'Focus colour', '2ranq-forms' ), 'default' => '#5b4be0' ), array( 'key' => 'field_focus_ring', 'type' => 'size', 'var' => 'field-focus-ring', 'label' => __( 'Focus ring width', '2ranq-forms' ), 'default' => '3', 'min' => 0, 'max' => 10 ), array( 'key' => 'field_error_color', 'type' => 'color', 'var' => 'field-error-color', 'label' => __( 'Error colour', '2ranq-forms' ), 'default' => '#dc2626' ), ), ), 'labels' => array( 'label' => __( 'Labels', '2ranq-forms' ), 'settings' => array( array( 'key' => 'label_color', 'type' => 'color', 'var' => 'label-color', 'label' => __( 'Colour', '2ranq-forms' ), 'default' => '#16181d' ), array( 'key' => 'label_size', 'type' => 'size', 'var' => 'label-size', 'label' => __( 'Text size', '2ranq-forms' ), 'default' => '15', 'min' => 10, 'max' => 28 ), array( 'key' => 'label_weight', 'type' => 'select', 'var' => 'label-weight', 'label' => __( 'Weight', '2ranq-forms' ), 'default' => '600', 'options' => array( '400' => array( __( 'Normal', '2ranq-forms' ), '400' ), '500' => array( __( 'Medium', '2ranq-forms' ), '500' ), '600' => array( __( 'Semi bold', '2ranq-forms' ), '600' ), '700' => array( __( 'Bold', '2ranq-forms' ), '700' ), ), ), array( 'key' => 'label_gap', 'type' => 'size', 'var' => 'label-gap', 'label' => __( 'Space under label', '2ranq-forms' ), 'default' => '6', 'min' => 0, 'max' => 24 ), array( 'key' => 'label_transform', 'type' => 'select', 'var' => 'label-transform', 'label' => __( 'Letter case', '2ranq-forms' ), 'default' => 'none', 'options' => array( 'none' => array( __( 'As typed', '2ranq-forms' ), 'none' ), 'uppercase' => array( __( 'All capitals', '2ranq-forms' ), 'uppercase' ), ), ), array( 'key' => 'label_tracking', 'type' => 'select', 'var' => 'label-tracking', 'label' => __( 'Letter spacing', '2ranq-forms' ), 'default' => 'normal', 'options' => array( 'normal' => array( __( 'Normal', '2ranq-forms' ), 'normal' ), 'wide' => array( __( 'Wide', '2ranq-forms' ), '.08em' ), 'tight' => array( __( 'Tight', '2ranq-forms' ), '-.01em' ), ), ), array( 'key' => 'desc_color', 'type' => 'color', 'var' => 'desc-color', 'label' => __( 'Description colour', '2ranq-forms' ), 'default' => '#5c6270' ), array( 'key' => 'desc_size', 'type' => 'size', 'var' => 'desc-size', 'label' => __( 'Description size', '2ranq-forms' ), 'default' => '13', 'min' => 9, 'max' => 20 ), array( 'key' => 'required_color', 'type' => 'color', 'var' => 'required-color', 'label' => __( 'Required mark colour', '2ranq-forms' ), 'default' => '#dc2626' ), ), ), 'button' => array( 'label' => __( 'Button', '2ranq-forms' ), 'settings' => array( array( 'key' => 'button_bg', 'type' => 'color', 'var' => 'button-bg', 'label' => __( 'Background', '2ranq-forms' ), 'default' => '#16181d' ), array( 'key' => 'button_text', 'type' => 'color', 'var' => 'button-text', 'label' => __( 'Text colour', '2ranq-forms' ), 'default' => '#ffffff' ), array( 'key' => 'button_bg_hover', 'type' => 'color', 'var' => 'button-bg-hover', 'label' => __( 'Background on hover', '2ranq-forms' ), 'default' => '#2b2b2b' ), array( 'key' => 'button_text_hover', 'type' => 'color', 'var' => 'button-text-hover', 'label' => __( 'Text on hover', '2ranq-forms' ), 'default' => '#ffffff' ), array( 'key' => 'button_border_width', 'type' => 'size', 'var' => 'button-border-width', 'label' => __( 'Border width', '2ranq-forms' ), 'default' => '0', 'min' => 0, 'max' => 8 ), array( 'key' => 'button_border_color', 'type' => 'color', 'var' => 'button-border-color', 'label' => __( 'Border colour', '2ranq-forms' ), 'default' => '#16181d' ), array( 'key' => 'button_radius', 'type' => 'size', 'var' => 'button-radius', 'label' => __( 'Corner radius', '2ranq-forms' ), 'default' => '6', 'min' => 0, 'max' => 40 ), array( 'key' => 'button_weight', 'type' => 'select', 'var' => 'button-weight', 'label' => __( 'Text weight', '2ranq-forms' ), 'default' => '600', 'options' => array( '400' => array( __( 'Normal', '2ranq-forms' ), '400' ), '500' => array( __( 'Medium', '2ranq-forms' ), '500' ), '600' => array( __( 'Semi bold', '2ranq-forms' ), '600' ), '700' => array( __( 'Bold', '2ranq-forms' ), '700' ), ), ), array( 'key' => 'button_shadow', 'type' => 'select', 'var' => 'button-shadow', 'label' => __( 'Shadow', '2ranq-forms' ), 'default' => 'none', 'options' => array( 'none' => array( __( 'None', '2ranq-forms' ), 'none' ), 'soft' => array( __( 'Soft', '2ranq-forms' ), '0 4px 14px rgba(0,0,0,.16)' ), // The glow takes the colour of the button itself, so it works // whether the button is lime or navy. Without `color-mix` every // colour would need its own rule. 'glow' => array( __( 'Glow', '2ranq-forms' ), '0 0 30px color-mix(in srgb, var(--ranq-button-bg) 26%, transparent)' ), ), ), array( 'key' => 'button_font_size', 'type' => 'size', 'var' => 'button-font-size', 'label' => __( 'Text size', '2ranq-forms' ), 'default' => '16', 'min' => 11, 'max' => 28 ), array( 'key' => 'button_padding_y', 'type' => 'size', 'var' => 'button-padding-y', 'label' => __( 'Height spacing', '2ranq-forms' ), 'default' => '12', 'min' => 4, 'max' => 32 ), array( 'key' => 'button_padding_x', 'type' => 'size', 'var' => 'button-padding-x', 'label' => __( 'Side spacing', '2ranq-forms' ), 'default' => '24', 'min' => 6, 'max' => 64 ), array( 'key' => 'button_width', 'type' => 'select', 'var' => 'button-width', 'label' => __( 'Width', '2ranq-forms' ), 'default' => 'auto', 'options' => array( 'auto' => array( __( 'Fit text', '2ranq-forms' ), 'auto' ), 'full' => array( __( 'Full width', '2ranq-forms' ), '100%' ), ), ), array( 'key' => 'button_align', 'type' => 'select', 'var' => 'button-align', 'label' => __( 'Alignment', '2ranq-forms' ), 'default' => 'start', 'options' => array( 'start' => array( __( 'Left', '2ranq-forms' ), 'start' ), 'center' => array( __( 'Centre', '2ranq-forms' ), 'center' ), 'end' => array( __( 'Right', '2ranq-forms' ), 'end' ), ), ), ), ), 'file' => array( 'label' => __( 'File upload', '2ranq-forms' ), 'settings' => array( array( 'key' => 'file_bg', 'type' => 'color', 'var' => 'file-bg', 'label' => __( 'Background', '2ranq-forms' ), 'default' => 'transparent' ), array( 'key' => 'file_border_color', 'type' => 'color', 'var' => 'file-border-color', 'label' => __( 'Border colour', '2ranq-forms' ), 'default' => '#c9ccd4' ), array( 'key' => 'file_height', 'type' => 'size', 'var' => 'file-height', 'label' => __( 'Height', '2ranq-forms' ), 'default' => '96', 'min' => 48, 'max' => 240 ), ), ), 'steps' => array( 'label' => __( 'Steps', '2ranq-forms' ), 'settings' => array( /* * How the step bar looks. Five ready made looks, each written on * its own in `form.css`. The name of the chosen look is printed * by the page break field onto itself, as `data-step-look`, and * the style picks its rules through `:has()` on the form. Why * this way: the bar is built by `assets/js/form.js`, always with * the same class names, so there is nowhere to write the look * class. This way neither the form nor the script is touched, * and the builder and the site draw by the same rules. * * The default is `line`, that is exactly what the bar did until * now, so that no form already built changes by itself. * * 🔴 Without JavaScript there is no bar at all: every step is * shown at once and the form is sent as a plain one. The look of * the bar can therefore never be the reason a form does not work. */ array( 'key' => 'step_look', 'type' => 'select', 'var' => 'step-look', 'label' => __( 'Step bar', '2ranq-forms' ), 'default' => 'line', 'options' => array( 'line' => array( __( 'Line above the name', '2ranq-forms' ), 'line' ), 'bar' => array( __( 'One bar that fills up', '2ranq-forms' ), 'bar' ), 'numbers' => array( __( 'Numbers in circles', '2ranq-forms' ), 'numbers' ), 'tabs' => array( __( 'Named tabs', '2ranq-forms' ), 'tabs' ), 'dots' => array( __( 'Dots', '2ranq-forms' ), 'dots' ), ), ), array( 'key' => 'step_color', 'type' => 'color', 'var' => 'step-color', 'label' => __( 'Step not reached yet', '2ranq-forms' ), 'default' => '#c9ccd4' ), array( 'key' => 'step_current', 'type' => 'color', 'var' => 'step-current', 'label' => __( 'Current step', '2ranq-forms' ), 'default' => '#16181d' ), array( 'key' => 'step_done', 'type' => 'color', 'var' => 'step-done', 'label' => __( 'Finished step', '2ranq-forms' ), 'default' => '#16181d' ), array( 'key' => 'step_text', 'type' => 'color', 'var' => 'step-text', 'label' => __( 'Step name', '2ranq-forms' ), 'default' => '#5c6270' ), /* * The colour of the number and the name that sit ON a filled * step. It exists because the fill colour is chosen, so there is * no way to know in advance whether white or black reads on it: * white on a dark bar, but black on a yellow or lime one. */ array( 'key' => 'step_on', 'type' => 'color', 'var' => 'step-on', 'label' => __( 'Text on the filled step', '2ranq-forms' ), 'default' => '#ffffff' ), ), ), 'choices' => array( 'label' => __( 'Choices', '2ranq-forms' ), 'settings' => array( array( 'key' => 'choice_accent', 'type' => 'color', 'var' => 'choice-accent', 'label' => __( 'Selected colour', '2ranq-forms' ), 'default' => '#5b4be0' ), array( 'key' => 'choice_size', 'type' => 'size', 'var' => 'choice-size', 'label' => __( 'Box size', '2ranq-forms' ), 'default' => '18', 'min' => 12, 'max' => 32 ), array( 'key' => 'choice_gap', 'type' => 'size', 'var' => 'choice-gap', 'label' => __( 'Space between choices', '2ranq-forms' ), 'default' => '8', 'min' => 0, 'max' => 32 ), array( 'key' => 'choice_layout', 'type' => 'select', 'var' => 'choice-layout', 'label' => __( 'Layout', '2ranq-forms' ), 'default' => 'stack', 'options' => array( 'stack' => array( __( 'One under another', '2ranq-forms' ), 'grid' ), 'row' => array( __( 'In a row', '2ranq-forms' ), 'flex' ), ), ), ), ), ); /** * Adding design settings from modules. * * @param array $schema The description of the settings. */ return apply_filters( 'ranq_forms_design_schema', $schema ); } /** * The default values, pulled out of the description. * * @return array */ public static function defaults() { $defaults = array(); foreach ( self::schema() as $group ) { foreach ( $group['settings'] as $setting ) { $defaults[ $setting['key'] ] = $setting['default']; } } return $defaults; } /** * The design of one form, filled in with the defaults. * * @param int $form_id The form id. * * @return array */ public static function get( $form_id ) { $saved = get_post_meta( absint( $form_id ), self::META_KEY, true ); if ( ! is_array( $saved ) ) { $saved = array(); } return array_merge( self::defaults(), $saved ); } /** * Cleans the design values. * * Only what is in the description passes through, and only in the shape that * description allows. A colour that is not a colour is thrown away, a number * outside the limits is pulled back inside them, a choice that was not * offered is thrown away. * * @param array $values The raw values. * * @return array */ public static function sanitize( $values ) { if ( ! is_array( $values ) ) { return array(); } $clean = array(); foreach ( self::schema() as $group ) { foreach ( $group['settings'] as $setting ) { $key = $setting['key']; if ( ! isset( $values[ $key ] ) ) { continue; } $value = $values[ $key ]; switch ( $setting['type'] ) { case 'color': $clean[ $key ] = self::sanitize_color( $value, $setting['default'] ); break; case 'size': $min = isset( $setting['min'] ) ? (float) $setting['min'] : 0; $max = isset( $setting['max'] ) ? (float) $setting['max'] : 9999; if ( ! is_numeric( $value ) ) { break; } $clean[ $key ] = (string) (int) max( $min, min( $max, (float) $value ) ); break; case 'select': if ( isset( $setting['options'][ $value ] ) ) { $clean[ $key ] = (string) $value; } break; case 'toggle': $clean[ $key ] = (bool) $value; break; case 'image': $url = is_scalar( $value ) ? esc_url_raw( trim( (string) $value ) ) : ''; // A plain address only. No `javascript:`, no `data:`, nothing // that could run code through the style. $clean[ $key ] = preg_match( '#^https?://#i', $url ) ? $url : ''; break; default: $clean[ $key ] = sanitize_text_field( is_scalar( $value ) ? (string) $value : '' ); break; } } } return $clean; } /** * Cleans a colour. Accepts `#rrggbb`, `#rgb`, `rgba(...)` and the word * `transparent`. * * @param mixed $value The raw value. * @param string $fallback What is returned when the value is not a colour. * * @return string */ private static function sanitize_color( $value, $fallback ) { $value = is_scalar( $value ) ? trim( (string) $value ) : ''; if ( 'transparent' === $value || 'inherit' === $value ) { return $value; } $hex = sanitize_hex_color( $value ); if ( $hex ) { return $hex; } if ( preg_match( '/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(,\s*(0|1|0?\.\d+)\s*)?\)$/', $value ) ) { return $value; } return $fallback; } /** * Builds the list of custom properties for one form. * * Only what differs from the default is printed, so the attribute stays * short. The rest already sits in our style sheet. * * @param int $form_id The form id. * * @return string The value for the `style` attribute, or an empty string. */ public static function inline_style( $form_id ) { $values = self::get( $form_id ); $defaults = self::defaults(); $parts = array(); foreach ( self::schema() as $group ) { foreach ( $group['settings'] as $setting ) { $key = $setting['key']; if ( ! isset( $values[ $key ] ) || $values[ $key ] === $defaults[ $key ] ) { continue; } $parts[] = '--ranq-' . $setting['var'] . ':' . self::css_value( $setting, $values[ $key ] ); } } return $parts ? implode( ';', $parts ) . ';' : ''; } /** * Turns a stored value into what goes into the style. * * @param array $setting The description of the setting. * @param mixed $value The value. * * @return string */ public static function css_value( $setting, $value ) { switch ( $setting['type'] ) { case 'size': return (string) (int) $value . 'px'; case 'select': return isset( $setting['options'][ $value ][1] ) ? (string) $setting['options'][ $value ][1] : ''; case 'image': $url = (string) $value; return '' === $url ? 'none' : 'url("' . esc_url( $url ) . '")'; default: return (string) $value; } } }