CMS Project Sync

This commit is contained in:
2026-04-15 15:59:53 -04:00
parent 015ea75186
commit a747e2a1d9
11220 changed files with 2590467 additions and 0 deletions
@@ -0,0 +1,773 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Checkbox field.
*
* @since 1.0.0
*/
class WPForms_Field_Checkbox extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Checkboxes', 'wpforms-lite' );
$this->keywords = esc_html__( 'choice', 'wpforms-lite' );
$this->type = 'checkbox';
$this->icon = 'fa-check-square-o';
$this->order = 110;
$this->defaults = [
1 => [
'label' => esc_html__( 'First Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
2 => [
'label' => esc_html__( 'Second Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
3 => [
'label' => esc_html__( 'Third Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
private function hooks() {
// Customize HTML field values.
add_filter( 'wpforms_html_field_value', [ $this, 'field_html_value' ], 10, 4 );
add_filter( "wpforms_{$this->type}_field_html_value_images", [ $this, 'field_html_value_images' ], 10, 3 );
// Define additional field properties.
add_filter( 'wpforms_field_properties_checkbox', [ $this, 'field_properties' ], 5, 3 );
// This field requires fieldset+legend instead of the field label.
add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", '__return_true', PHP_INT_MAX, 2 );
}
/**
* Define additional field properties.
*
* @since 1.4.5
*
* @param array $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$choices = $field['choices'];
$dynamic = wpforms_get_field_dynamic_choices( $field, $form_id, $form_data );
if ( $dynamic !== false ) {
$choices = $dynamic;
$field['show_values'] = true;
}
// Remove primary input, unset for attribute for label.
unset( $properties['inputs']['primary'], $properties['label']['attr']['for'] );
// Set input container (ul) properties.
$properties['input_container'] = [
'class' => [ ! empty( $field['random'] ) ? 'wpforms-randomize' : '' ],
'data' => [],
'attr' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
];
$is_choice_limit_set = ! empty( $field['choice_limit'] ) && (int) $field['choice_limit'] > 0;
if ( $is_choice_limit_set ) {
$properties['input_container']['data']['choice-limit'] = $field['choice_limit'];
}
// Set input properties.
foreach ( $choices as $key => $choice ) {
// Used for dynamic choices.
$depth = isset( $choice['depth'] ) ? absint( $choice['depth'] ) : 1;
$label = isset( $choice['label'] ) ? $choice['label'] : '';
// Choice labels should not be left blank, but if they are we
// provide a basic value.
$value = isset( $field['show_values'] ) ? $choice['value'] : $label;
if ( '' === $value ) {
if ( 1 === count( $choices ) ) {
$value = esc_html__( 'Checked', 'wpforms-lite' );
} else {
/* translators: %s - choice number. */
$value = sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key );
}
}
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}", "depth-{$depth}" ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $label,
],
'attr' => [
'name' => "wpforms[fields][{$field_id}][]",
'value' => $value,
],
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'icon' => isset( $choice['icon'] ) ? $choice['icon'] : '',
'icon_style' => isset( $choice['icon_style'] ) ? $choice['icon_style'] : '',
'image' => isset( $choice['image'] ) ? $choice['image'] : '',
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => isset( $choice['default'] ),
];
// Rule for validator only if needed.
if ( $is_choice_limit_set ) {
$properties['inputs'][ $key ]['data']['rule-check-limit'] = 'true';
}
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
// Custom properties if image choices is enabled.
if ( ! $dynamic && ! empty( $field['choices_images'] ) ) {
$properties['input_container']['class'][] = 'wpforms-image-choices';
$properties['input_container']['class'][] = 'wpforms-image-choices-' . sanitize_html_class( $field['choices_images_style'] );
foreach ( $properties['inputs'] as $key => $inputs ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-image-choices-item';
if ( in_array( $field['choices_images_style'], [ 'modern', 'classic' ], true ) ) {
$properties['inputs'][ $key ]['class'][] = 'wpforms-screen-reader-element';
}
}
} elseif ( ! $dynamic && ! empty( $field['choices_icons'] ) ) {
$properties = wpforms()->obj( 'icon_choices' )->field_properties( $properties, $field );
}
// Custom properties for disclaimer format display.
if ( ! empty( $field['disclaimer_format'] ) ) {
$properties['description']['class'][] = 'wpforms-disclaimer-description';
$properties['description']['value'] = nl2br( $properties['description']['value'] );
}
// Add selected class for choices with defaults.
foreach ( $properties['inputs'] as $key => $inputs ) {
if ( ! empty( $inputs['default'] ) ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-selected';
}
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option( 'choices', $field );
// AI Feature.
$this->field_option(
'ai_modal_button',
$field,
[
'value' => esc_html__( 'Generate Choices', 'wpforms-lite' ),
'type' => 'choices',
]
);
// Choices Images.
$this->field_option( 'choices_images', $field );
// Hide Choices Images.
$this->field_option( 'choices_images_hide', $field );
// Choices Images Style (theme).
$this->field_option( 'choices_images_style', $field );
// Choices Icons.
$this->field_option( 'choices_icons', $field );
// Choices Icons Color.
$this->field_option( 'choices_icons_color', $field );
// Choices Icons Size.
$this->field_option( 'choices_icons_size', $field );
// Choices Icons Style.
$this->field_option( 'choices_icons_style', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Randomize order of choices.
$this->field_element(
'row',
$field,
[
'slug' => 'random',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'random',
'value' => isset( $field['random'] ) ? '1' : '0',
'desc' => esc_html__( 'Randomize Choices', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to randomize the order of the choices.', 'wpforms-lite' ),
],
false
),
]
);
// Show Values toggle option. This option will only show if already used
// or if manually enabled by a filter.
if ( ! empty( $field['show_values'] ) || wpforms_show_fields_options_setting() ) {
$this->field_element(
'row',
$field,
[
'slug' => 'show_values',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'show_values',
'value' => isset( $field['show_values'] ) ? $field['show_values'] : '0',
'desc' => esc_html__( 'Show Values', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to manually set form field values.', 'wpforms-lite' ),
],
false
),
]
);
}
// Display format.
$this->field_option( 'input_columns', $field );
// Choice Limit.
$this->field_option( 'choice_limit', $field );
// Dynamic choice auto-populating toggle.
$this->field_option( 'dynamic_choices', $field );
// Dynamic choice source.
$this->field_option( 'dynamic_choices_source', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Enable Disclaimer formatting.
$this->field_element(
'row',
$field,
[
'slug' => 'disclaimer_format',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'disclaimer_format',
'value' => isset( $field['disclaimer_format'] ) ? '1' : '0',
'desc' => esc_html__( 'Enable Disclaimer / Terms of Service Display', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to adjust the field styling to support Disclaimers and Terms of Service type agreements.', 'wpforms-lite' ),
],
false
),
]
);
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Choices.
$this->field_preview_option( 'choices', $field );
// Description.
$this->field_preview_option(
'description',
$field,
[
'class' => ! empty( $field['disclaimer_format'] ) ? 'disclaimer nl2br' : false,
]
);
}
/**
* Field display on the form front-end and admin entry edit page.
*
* @since 1.0.0
*
* @param array $field Field settings.
* @param array $deprecated Deprecated array.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
$using_image_choices = empty( $field['dynamic_choices'] ) && ! empty( $field['choices_images'] );
$using_icon_choices = empty( $field['dynamic_choices'] ) && empty( $field['choices_images'] ) && ! empty( $field['choices_icons'] );
// Define data.
$container = $field['properties']['input_container'];
$choices = $field['properties']['inputs'];
// Do not display the field with empty choices on the frontend.
if ( ! $choices && ! is_admin() ) {
return;
}
// Display a warning message on Entry Edit page.
if ( ! $choices && is_admin() ) {
$this->display_empty_dynamic_choices_message( $field );
return;
}
$amp_state_id = '';
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$amp_state_id = str_replace( '-', '_', sanitize_key( $container['id'] ) ) . '_state';
$state = [];
foreach ( $choices as $key => $choice ) {
$state[ $choice['id'] ] = ! empty( $choice['default'] );
}
printf(
'<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
esc_attr( $amp_state_id ),
wp_json_encode( $state )
);
}
printf(
'<ul %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
foreach ( $choices as $key => $choice ) {
$label = $this->get_choices_label( $choice['label']['text'] ?? '', $key, $field );
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$choice['container']['attr']['[class]'] = sprintf(
'%s + ( %s[%s] ? " wpforms-selected" : "")',
wp_json_encode( implode( ' ', $choice['container']['class'] ) ),
$amp_state_id,
wp_json_encode( $choice['id'] )
);
}
// If the field is required, has the label hidden, and has
// disclaimer mode enabled, so the required status in choice
// label.
$required = '';
if ( ! empty( $field['disclaimer_format'] ) && ! empty( $choice['required'] ) && ! empty( $field['label_hide'] ) ) {
$required = wpforms_get_field_required_label();
}
printf(
'<li %s>',
wpforms_html_attributes( $choice['container']['id'], $choice['container']['class'], $choice['container']['data'], $choice['container']['attr'] )
);
// The required constraint in HTML5 form validation does not work with checkbox groups, so omit in AMP.
$required_attr = wpforms_is_amp() && count( $choices ) > 1 ? '' : $choice['required'];
if ( $using_image_choices ) {
// Make sure the image choices are keyboard-accessible.
$choice['label']['attr']['tabindex'] = 0;
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState({ %s: { %s: ! %s[%s] } })',
wp_json_encode( $amp_state_id ),
wp_json_encode( $choice['id'] ),
$amp_state_id,
wp_json_encode( $choice['id'] )
);
$choice['label']['attr']['role'] = 'button';
}
if ( is_array( $choice['label']['class'] ) && wpforms_is_empty_string( $label ) ) {
$choice['label']['class'][] = 'wpforms-field-label-inline-empty';
}
// Image choices.
printf(
'<label %s>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] )
);
echo '<span class="wpforms-image-choices-image">';
if ( ! empty( $choice['image'] ) ) {
printf(
'<img src="%s" alt="%s"%s>',
esc_url( $choice['image'] ),
esc_attr( $label ),
! empty( $label ) ? ' title="' . esc_attr( $label ) . '"' : ''
);
}
echo '</span>';
if ( $field['choices_images_style'] === 'none' ) {
echo '<br>';
}
$choice['attr']['tabindex'] = '-1';
if ( wpforms_is_amp() ) {
$choice['attr']['[checked]'] = sprintf(
'%s[%s]',
$amp_state_id,
wp_json_encode( $choice['id'] )
);
}
printf(
'<input type="checkbox" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $required_attr ),
checked( '1', $choice['default'], false )
);
echo '<span class="wpforms-image-choices-label">' . wp_kses_post( $label ) . '</span>';
echo '</label>';
} elseif ( $using_icon_choices ) {
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState({ %s: { %s: ! %s[%s] } })',
wp_json_encode( $amp_state_id ),
wp_json_encode( $choice['id'] ),
$amp_state_id,
wp_json_encode( $choice['id'] )
);
$choice['label']['attr']['role'] = 'button';
}
// Icon Choices.
wpforms()->obj( 'icon_choices' )->field_display( $field, $choice, 'checkbox' );
} else {
// Normal display.
printf(
'<input type="checkbox" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $required_attr ),
checked( '1', $choice['default'], false )
);
printf(
'<label %s>%s%s</label>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] ),
wp_kses_post( $label ),
wp_kses(
$required,
[
'span' => [
'class' => true,
],
]
)
);
}
echo '</li>';
}
echo '</ul>';
}
/**
* Validate field on form submit.
*
* @since 1.5.2
*
* @param int $field_id Field ID.
* @param array $field_submit Submitted field value (raw data).
* @param array $form_data Form data.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$field_id = (int) $field_id;
$field = $form_data['fields'][ $field_id ];
// Skip validation if field is dynamic and choices are empty.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
return;
}
$field_submit = (array) $field_submit;
$this->validate_field_choice_limit( $field_id, $field_submit, $form_data );
// Basic required check - If field is marked as required, check for entry data.
if (
! empty( $form_data['fields'][ $field_id ]['required'] ) &&
(
empty( $field_submit ) ||
(
count( $field_submit ) === 1 &&
( ! isset( $field_submit[0] ) || (string) $field_submit[0] === '' )
)
)
) {
$error = wpforms_get_required_label();
}
if ( ! empty( $error ) ) {
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = $error;
}
}
/**
* Format and sanitize field.
*
* @since 1.0.2
*
* @param int $field_id Field ID.
* @param array $field_submit Submitted form data.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field_submit = (array) $field_submit;
$field = $form_data['fields'][ $field_id ];
$dynamic = ! empty( $field['dynamic_choices'] ) ? $field['dynamic_choices'] : false;
$name = sanitize_text_field( $field['label'] );
$value_raw = wpforms_sanitize_array_combine( $field_submit );
$data = [
'name' => $name,
'value' => '',
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
if ( 'post_type' === $dynamic && ! empty( $field['dynamic_post_type'] ) ) {
// Dynamic population is enabled using post type.
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'post_type';
$data['dynamic_items'] = $value_raw;
$data['dynamic_post_type'] = $field['dynamic_post_type'];
$posts = [];
foreach ( $field_submit as $id ) {
$post = get_post( $id );
if ( ! is_wp_error( $post ) && ! empty( $post ) && $data['dynamic_post_type'] === $post->post_type ) {
$posts[] = esc_html( wpforms_get_post_title( $post ) );
}
}
$data['value'] = ! empty( $posts ) ? wpforms_sanitize_array_combine( $posts ) : '';
}
elseif ( 'taxonomy' === $dynamic && ! empty( $field['dynamic_taxonomy'] ) ) {
// Dynamic population is enabled using taxonomy.
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'taxonomy';
$data['dynamic_items'] = $value_raw;
$data['dynamic_taxonomy'] = $field['dynamic_taxonomy'];
$terms = [];
foreach ( $field_submit as $id ) {
$term = get_term( $id, $field['dynamic_taxonomy'] );
if ( ! is_wp_error( $term ) && ! empty( $term ) ) {
$terms[] = esc_html( wpforms_get_term_name( $term ) );
}
}
$data['value'] = ! empty( $terms ) ? wpforms_sanitize_array_combine( $terms ) : '';
} else {
// Normal processing, dynamic population is off.
$choice_keys = [];
// If show_values is true, that means values posted are the raw values
// and not the labels. So we need to set label values. Also store
// the choice keys.
if ( ! empty( $field['show_values'] ) && (int) $field['show_values'] === 1 ) {
foreach ( $field_submit as $item ) {
foreach ( $field['choices'] as $key => $choice ) {
// Check if the submitted value is the same as the choice value or if the value is empty and the key matches.
// Skip if the submitted value is empty.
if ( ( ! empty( $item ) && $item === $choice['value'] ) || ( empty( $choice['value'] ) && (int) str_replace( 'Choice ', '', $item ) === $key ) ) {
$value[] = $choice['label'];
$choice_keys[] = $key;
break;
}
}
}
$data['value'] = ! empty( $value ) ? wpforms_sanitize_array_combine( $value ) : '';
} else {
$data['value'] = $value_raw;
// Determine choices keys, this is needed for image choices.
foreach ( $field_submit as $item ) {
foreach ( $field['choices'] as $key => $choice ) {
/* translators: %s - choice number. */
if ( $item === $choice['label'] || $item === sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key ) ) {
$choice_keys[] = $key;
break;
}
}
}
}
// Images choices are enabled, lookup and store image URLs.
if ( ! empty( $choice_keys ) && ! empty( $field['choices_images'] ) ) {
$data['images'] = [];
foreach ( $choice_keys as $key ) {
$data['images'][] = ! empty( $field['choices'][ $key ]['image'] ) ? esc_url_raw( $field['choices'][ $key ]['image'] ) : '';
}
}
}
// Push field details to be saved.
wpforms()->obj( 'process' )->fields[ $field_id ] = $data;
}
}
new WPForms_Field_Checkbox();
@@ -0,0 +1,329 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* GDPR Checkbox field.
*
* @since 1.4.6
*/
class WPForms_Field_GDPR_Checkbox extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.4.6
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'GDPR Agreement', 'wpforms-lite' );
$this->type = 'gdpr-checkbox';
$this->icon = 'fa-check-square-o';
$this->order = 500;
$this->allow_read_only = false;
$this->defaults = [
1 => [
'label' => esc_html__( 'I consent to having this website store my submitted information so they can respond to my inquiry.', 'wpforms-lite' ),
'value' => '',
'image' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
// Set field to default to the required.
add_filter( 'wpforms_field_new_required', [ $this, 'field_default_required' ], 10, 2 );
// Define additional field properties.
add_filter( 'wpforms_field_properties_gdpr-checkbox', [ $this, 'field_properties' ], 5, 3 );
}
/**
* Field should default to being required.
*
* @since 1.4.6
*
* @param bool $required Required status, true is required.
* @param array $field Field settings.
*
* @return bool
*/
public function field_default_required( $required, $field ) {
if ( $this->type === $field['type'] ) {
return true;
}
return $required;
}
/**
* Define additional field properties.
*
* @since 1.4.6
*
* @param array $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ) {
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = absint( $field['id'] );
$choices = ! empty( $field['choices'] ) ? $field['choices'] : [];
// Remove primary input, unset for attribute for label.
unset( $properties['inputs']['primary'], $properties['label']['attr']['for'] );
// Set input container (ul) properties.
$properties['input_container'] = [
'class' => [],
'data' => [],
'attr' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
];
// Set input properties.
foreach ( $choices as $key => $choice ) {
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}" ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $choice['label'],
],
'attr' => [
'name' => "wpforms[fields][{$field_id}][]",
'value' => $choice['label'],
],
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'image' => '',
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => '',
];
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
return $properties;
}
/**
* Whether the current field can be populated dynamically.
*
* @since 1.9.4
*
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return bool
*/
public function is_dynamic_population_allowed( $properties, $field ): bool {
return false;
}
/**
* Field options panel inside the builder.
*
* @since 1.4.6
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
// Field is always required.
$this->field_element(
'text',
$field,
[
'type' => 'hidden',
'slug' => 'required',
'value' => '1',
]
);
// -------------------------------------------------------------------//
// Basic field options
// -------------------------------------------------------------------//
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option(
'choices',
$field,
[
'label' => esc_html__( 'Agreement', 'wpforms-lite' ),
]
);
// Description.
$this->field_option( 'description', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
// -------------------------------------------------------------------//
// Advanced field options
// -------------------------------------------------------------------//
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.4.6
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Choices.
$this->field_preview_option( 'choices', $field );
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.4.6
*
* @param array $field Field settings.
* @param array $deprecated Deprecated array.
* @param array $form_data Form data and settings.
*
* @noinspection HtmlUnknownAttribute
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$container = $field['properties']['input_container'];
$choices = $field['properties']['inputs'];
printf(
'<ul %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
foreach ( $choices as $choice ) {
$required = '';
if ( ! empty( $choice['required'] ) && ! empty( $field['label_hide'] ) ) {
$required = wpforms_get_field_required_label();
}
printf(
'<li %s>',
wpforms_html_attributes( $choice['container']['id'], $choice['container']['class'], $choice['container']['data'], $choice['container']['attr'] )
);
// Normal display.
printf(
'<input type="checkbox" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $choice['required'] ),
checked( '1', $choice['default'], false )
);
printf(
'<label %s>%s%s</label>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] ),
wp_kses_post( $choice['label']['text'] ),
wp_kses_post( $required )
);
echo '</li>';
}
echo '</ul>';
}
/**
* Format and sanitize field.
*
* @since 1.4.6
*
* @param int $field_id Field ID.
* @param array $field_submit Submitted form data.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => ! empty( $form_data['fields'][ $field_id ]['label'] ) ? sanitize_text_field( $form_data['fields'][ $field_id ]['label'] ) : '',
'value' => $form_data['fields'][ $field_id ]['choices'][1]['label'],
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
}
new WPForms_Field_GDPR_Checkbox();
@@ -0,0 +1,942 @@
<?php
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Internal information field class.
*
* @since 1.7.6
*/
class WPForms_Field_Internal_Information extends WPForms_Field {
/**
* The key used to save form checkboxes in the post meta table.
*
* @since 1.7.6
*
* @var string
*/
private const CHECKBOX_META_KEY = 'wpforms_iif_checkboxes';
/**
* Class initialization method.
*
* @since 1.7.6
*/
public function init() {
$this->name = $this->is_editable() ? esc_html__( 'Internal Information', 'wpforms-lite' ) : esc_html__( 'This field is not editable', 'wpforms-lite' );
$this->type = 'internal-information';
$this->icon = 'fa fa-sticky-note-o';
$this->order = 550;
$this->hooks();
}
/**
* Register hooks.
*
* @since 1.7.6
*
* @noinspection PhpUnnecessaryCurlyVarSyntaxInspection
*/
private function hooks() {
add_filter( 'wpforms_entries_table_fields_disallow', [ $this, 'hide_column_in_entries_table' ], 10, 2 );
add_filter( 'wpforms_field_preview_class', [ $this, 'add_css_class_for_field_wrapper' ], 10, 2 );
add_filter( 'wpforms_field_new_class', [ $this, 'add_css_class_for_field_wrapper' ], 10, 2 );
add_filter( "wpforms_pro_admin_entries_edit_is_field_displayable_{$this->type}", '__return_false' );
add_filter( 'wpforms_builder_strings', [ $this, 'builder_strings' ], 10, 2 );
add_filter( 'wpforms_frontend_form_data', [ $this, 'remove_internal_fields_on_front_end' ] );
add_filter( 'wpforms_pro_fields_entry_preview_get_ignored_fields', [ $this, 'ignore_entry_preview' ] );
add_filter( 'wpforms_process_before_form_data', [ $this, 'process_before_form_data' ], 10, 2 );
add_filter( 'wpforms_field_preview_display_duplicate_button', [ $this, 'display_duplicate_button' ], 10, 3 );
add_action( 'wpforms_builder_enqueues', [ $this, 'builder_enqueues' ] );
add_action( 'wp_ajax_wpforms_builder_save_internal_information_checkbox', [ $this, 'save_internal_information_checkbox' ] );
}
/**
* Whether the current field can be populated dynamically.
*
* @since 1.7.6
*
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return bool
*/
public function is_dynamic_population_allowed( $properties, $field ): bool {
return false;
}
/**
* Whether the current field can be populated using a fallback.
*
* @since 1.7.6
*
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return bool
*/
public function is_fallback_population_allowed( $properties, $field ): bool {
return false;
}
/**
* Define field options to display in the left panel.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
public function field_options( $field ) {
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
$this->heading_option( $field );
$this->field_option( 'description', $field );
$this->expanded_description_option( $field );
$this->cta_label_option( $field );
$this->cta_link_option( $field );
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
$this->field_code( $field );
}
/**
* Define field preview on the right side on builder.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
public function field_preview( $field ) {
$class = wpforms_sanitize_classes( $field['class'] ?? '' );
printf(
'<div class="internal-information-wrap wpforms-clear %s">',
esc_attr( $class )
);
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo wpforms_render( 'fields/internal-information/icon-lightbulb' );
echo '<div class="internal-information-content">';
$this->render_preview( 'heading', $field );
$this->render_preview( 'description', $field );
$this->render_preview( 'expanded-description', $field );
$this->render_preview( 'addon', $field );
if ( $this->is_button_displayable( $field ) ) {
echo '<div class="wpforms-field-internal-information-row wpforms-field-internal-information-row-cta-button">';
echo $this->render_custom_preview( 'cta-button', $field );
echo '</div>';
}
echo '</div>';
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
/**
* Checks if the button is displayable.
*
* @since 1.7.6
*
* @param array $field Field data.
*
* @return bool
*/
private function is_button_displayable( $field ): bool {
return ! empty( $field['expanded-description'] ) ||
( ! empty( $field['cta-label'] ) && ! empty( $field['cta-link'] ) ) ||
$this->is_editable();
}
/**
* Stub to make the field not visible in the front-end.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
* @param array $deprecated Field attributes.
* @param array $form_data Form data.
*/
public function field_display( $field, $deprecated, $form_data ) {
}
/**
* Heading option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function heading_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'heading',
'value' => esc_html__( 'Heading', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter text for the form field heading.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'text',
$field,
[
'slug' => 'label',
'value' => ! empty( $field['label'] ) ? esc_attr( $field['label'] ) : '',
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'heading',
'content' => $output,
]
);
}
/**
* Expanded description option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function expanded_description_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'expanded-description',
'value' => esc_html__( 'Expanded Content', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter text for the form field expanded description.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'textarea',
$field,
[
'slug' => 'expanded-description',
'value' => ! empty( $field['expanded-description'] ) ? esc_html( $field['expanded-description'] ) : '',
],
false
);
$output .= sprintf(
'<p class="note">%s</p>',
esc_html__( 'Adds an expandable content area below the description.', 'wpforms-lite' )
);
$this->field_element(
'row',
$field,
[
'slug' => 'expanded-description',
'content' => $output,
]
);
}
/**
* CTA label option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function cta_label_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'cta-label',
'value' => esc_html__( 'CTA Label', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter label for the form field call to action button. The label will be ignored if the field has extended description content: in that case button will be used to expand the description content.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'text',
$field,
[
'slug' => 'cta-label',
'value' => ! empty( $field['cta-label'] ) ? esc_attr( $field['cta-label'] ) : esc_attr__( 'Learn More', 'wpforms-lite' ),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'cta-label',
'content' => $output,
]
);
}
/**
* CTA link option.
*
* @since 1.7.6
*
* @param array $field Field data and settings.
*/
private function cta_link_option( $field ) {
$output = $this->field_element(
'label',
$field,
[
'slug' => 'cta-link',
'value' => esc_html__( 'CTA Link', 'wpforms-lite' ),
'tooltip' => esc_attr__( 'Enter the URL for the form field call to action button. URL will be ignored if the field has extended description content: in that case button will be used to expand the description content.', 'wpforms-lite' ),
],
false
);
$output .= $this->field_element(
'text',
$field,
[
'slug' => 'cta-link',
'value' => ! empty( $field['cta-link'] ) ? esc_url( $field['cta-link'] ) : '',
],
false
);
$output .= sprintf(
'<p class="note">%s</p>',
esc_html__( 'CTA is hidden if Expanded Content is used.', 'wpforms-lite' )
);
$this->field_element(
'row',
$field,
[
'slug' => 'cta-link',
'content' => $output,
]
);
}
/**
* Add hidden input with code identifier.
*
* @since 1.8.9
*
* @param array $field Field data and settings.
*/
private function field_code( $field ) {
$this->field_element(
'row',
$field,
[
'slug' => 'code',
'content' => sprintf(
'<input type="hidden" name="fields[%1$s][code]" value="%2$s">',
$field['id'],
! empty( $field['code'] ) ? esc_attr( $field['code'] ) : ''
),
]
);
}
/**
* Add a CSS class to hide field settings when the field is not editable.
*
* @since 1.7.6
*
* @param string $option Field option to render.
* @param array $field Field data and settings.
* @param array $args Field preview arguments.
* @param bool $do_echo Print or return the value. Print by default.
*
* @return string|null
*/
public function field_element( $option, $field, $args = [], $do_echo = true ) {
if ( ! isset( $args['class'] ) ) {
$args['class'] = '';
}
if ( ! $this->is_editable() ) {
$args['class'] .= ' wpforms-hidden ';
}
return parent::field_element( $option, $field, $args, $do_echo );
}
/**
* Render a custom option preview on the right side of the builder.
*
* @since 1.7.6
*
* @param string $option Field option to render.
* @param array $field Field data and settings.
* @param array $args Field arguments.
*
* @return string
* @noinspection HtmlUnknownTarget
*/
private function render_custom_preview( $option, $field, $args = [] ): string { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$class = ! empty( $args['class'] ) ? wpforms_sanitize_classes( $args['class'] ) : '';
$allowed_tags = $this->get_allowed_tags();
switch ( $option ) {
case 'heading':
$label = isset( $field['label'] ) && ! wpforms_is_empty_string( $field['label'] ) ? esc_html( $field['label'] ) : '';
if ( ! $label ) {
$class .= ' hidden ';
}
return sprintf(
'<label class="label-title heading %s"><span class="text">%s</span><span class="required">*</span></label>',
esc_attr( $class ),
esc_html( $label )
);
case 'description': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
$description = ! empty( $field['description'] ) ? wp_kses( $field['description'], $allowed_tags ) : '';
$description = wpautop( $this->replace_checkboxes( $description, $field ) );
$description = $this->add_link_attributes( $description );
return sprintf( '<div class="description %s">%s</div>', $class, $description );
case 'expanded-description': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
$description = isset( $field['expanded-description'] ) && ! wpforms_is_empty_string( $field['expanded-description'] ) ? wp_kses( $field['expanded-description'], $allowed_tags ) : '';
$description = wpautop( $this->replace_checkboxes( $description, $field ) );
$description = $this->add_link_attributes( $description );
return sprintf( '<div class="expanded-description %s">%s</div>', esc_attr( $class ), wp_kses( $description, $allowed_tags ) );
case 'cta-button': // phpcs:ignore WPForms.Formatting.Switch.AddEmptyLineBefore
$label = ! empty( $field['cta-label'] ) && empty( $field['expanded-description'] ) ? esc_attr( $field['cta-label'] ) : esc_attr__( 'Learn More', 'wpforms-lite' );
if ( ! empty( $field['expanded-description'] ) ) {
return sprintf(
'<div class="cta-button cta-expand-description not-expanded %s"><a href="#" target="_blank" rel="noopener noreferrer"><span class="button-label">%s</span> %s %s</a></div>',
esc_attr( $class ),
esc_html( $label ),
wpforms_render( 'fields/internal-information/icon-not-expanded' ),
wpforms_render( 'fields/internal-information/icon-expanded' )
);
}
if ( ! empty( $field['cta-link'] ) ) {
return sprintf( '<div class="cta-button cta-link-external %s"><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></div>', esc_attr( $class ), esc_url( $this->add_url_utm( $field ) ), esc_html( $label ) );
}
return sprintf( '<div class="cta-button cta-link-external %s"><a href="" target="_blank" rel="noopener noreferrer" class="hidden"><span class="button-label"></span></a></div>', esc_attr( $class ) );
case 'addon':
if ( empty( $field['addon'] ) ) {
return '';
}
return sprintf( '<input type="hidden" name="fields[%1$s][addon]" value="%2$s">', esc_attr( $field['id'] ), esc_attr( $field['addon'] ) );
}
return '';
}
/**
* Display the field button in the left panel only if the field is editable.
*
* @since 1.7.6
*
* @param array $fields All fields to display in the left panel.
*
* @return array
*/
public function field_button( $fields ) {
if ( $this->is_editable() ) {
return parent::field_button( $fields );
}
return $fields;
}
/**
* When the form is going to be displayed on the front-end, remove internal information fields.
*
* @since 1.7.6
*
* @param array $form_data Form data.
*
* @return array
*/
public function remove_internal_fields_on_front_end( $form_data ) {
if ( empty( $form_data['fields'] ) ) {
return $form_data;
}
foreach ( $form_data['fields'] as $id => $field ) {
if ( $field['type'] === $this->type ) {
unset( $form_data['fields'][ $id ] );
}
}
return $form_data;
}
/**
* Add the internal information field to the list of ignored fields for entry preview.
*
* @since 1.9.1
*
* @param array|mixed $ignored_fields Ignored fields.
*
* @return array
*/
public function ignore_entry_preview( $ignored_fields ): array {
$ignored_fields = (array) $ignored_fields;
$ignored_fields[] = $this->type;
return $ignored_fields;
}
/**
* Remove field from form data before processing the form submit.
*
* @since 1.7.6
*
* @param array $form_data Form data.
* @param array $entry Form submission raw data ($_POST).
*
* @return array
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function process_before_form_data( $form_data, $entry ) {
return $this->remove_internal_fields_on_front_end( $form_data );
}
/**
* Do not display the duplicate button.
*
* @since 1.7.6
*
* @param bool $is_visible If true, the duplicate button will be displayed.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
*
* @return bool
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function display_duplicate_button( $is_visible, $field, $form_data ) {
if ( $this->is_internal_information_field( $field ) && ! $this->is_editable() ) {
return false;
}
return $is_visible;
}
/**
* Hide the column from the entry list table.
*
* @since 1.7.6
*
* @param array|mixed $disallowed Table columns.
*
* @return array
*/
public function hide_column_in_entries_table( $disallowed ): array {
$disallowed = (array) $disallowed;
$disallowed[] = $this->type;
return $disallowed;
}
/**
* Add a CSS class for the field parent div informing about mode (editable or not).
*
* @since 1.7.6
*
* @param string $css CSS classes.
* @param array $field Field data and settings.
*
* @return string
*/
public function add_css_class_for_field_wrapper( $css, $field ) {
if ( ! $this->is_internal_information_field( $field ) ) {
return $css;
}
// If the Internal Information field is added by some add-ons, it will be hidden by default.
// Add styles to the addon assets to display the field.
// When the addon is disabled, the field is hidden.
if ( ! empty( $field['addon'] ) ) {
$css .= sprintf( ' wpforms-field-internal-information-%s-addon wpforms-hidden', $field['addon'] );
}
if ( $this->is_editable() ) {
$css .= ' internal-information-editable ';
return $css;
}
$css .= ' ui-sortable-disabled internal-information-not-editable internal-information-not-draggable ';
return str_replace( 'ui-sortable-handle', '', $css );
}
/**
* Save the checkbox state to the post meta table.
*
* @since 1.7.6
*/
public function save_internal_information_checkbox(): void {
$form_id = isset( $_POST['formId'] ) ? absint( $_POST['formId'] ) : 0;
// Run several checks: required items, security, permissions.
if (
! $form_id ||
! isset( $_POST['name'], $_POST['checked'] ) ||
! check_ajax_referer( 'wpforms-builder', 'nonce', false ) ||
! wpforms_current_user_can( 'edit_forms', $form_id )
) {
wp_send_json_error();
}
$checked = (int) $_POST['checked'];
$name = sanitize_text_field( wp_unslash( $_POST['name'] ) );
$post_meta = get_post_meta( $form_id, self::CHECKBOX_META_KEY, true );
$post_meta = ! empty( $post_meta ) ? (array) $post_meta : [];
if ( $checked ) {
$post_meta[ $name ] = $checked;
} else {
unset( $post_meta[ $name ] );
}
update_post_meta( $form_id, self::CHECKBOX_META_KEY, $post_meta );
wp_send_json_success();
}
/**
* Localized strings for a wpforms-internal-information-field JS script.
*
* @since 1.7.6
*
* @param array $strings Localized strings.
* @param array $form The form element.
*
* @return array
* @noinspection PhpUnusedParameterInspection
*/
public function builder_strings( $strings, $form ) {
$strings['iif_redirect_url_field_error'] = esc_html__( 'You should enter a valid absolute address to the CTA Link field or leave it empty.', 'wpforms-lite' );
$strings['iif_dismiss'] = esc_html__( 'Dismiss', 'wpforms-lite' );
$strings['iif_more'] = esc_html__( 'Learn More', 'wpforms-lite' );
return $strings;
}
/**
* Enqueue wpforms-internal-information-field script.
*
* @since 1.7.6
*
* @param string $view Current view.
*
* @noinspection PhpUnusedParameterInspection, PhpUnnecessaryCurlyVarSyntaxInspection
*/
public function builder_enqueues( $view ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-md5-hash',
WPFORMS_PLUGIN_URL . 'assets/lib/md5.min.js',
[ 'wpforms-builder' ],
'2.19.0',
false
);
wp_enqueue_script(
'wpforms-internal-information-field',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/fields/internal-information{$min}.js",
[ 'wpforms-builder', 'wpforms-md5-hash' ],
WPFORMS_VERSION,
false
);
}
/**
* Checks if the user is allowed to edit the field's content.
*
* @since 1.7.6
*
* @return bool
*/
private function is_editable(): bool {
/**
* Allow changing a mode.
*
* @since 1.7.6
*
* @param bool $is_editable True if editable mode is allowed. Default: false.
*/
return (bool) apply_filters( 'wpforms_field_internal_information_is_editable', false );
}
/**
* Check if the field has type internal-information.
*
* @since 1.7.6
*
* @param array $field Field data.
*
* @return bool
*/
private function is_internal_information_field( $field ): bool {
return isset( $field['type'] ) && $field['type'] === $this->type;
}
/**
* Render the result of the field_preview_option into a custom div.
*
* If the field has no value, do not echo anything.
*
* @since 1.7.6
*
* @param string $label Field label.
* @param array $field Field settings and data.
* @param array $args Field arguments.
*
* @noinspection PhpSameParameterValueInspection
*/
private function render_preview( $label, $field, $args = [] ): void {
$key = $label === 'heading' ? 'label' : $label;
if ( empty( $field[ $key ] ) && ! $this->is_editable() ) {
return;
}
$allowed_tags = $this->get_allowed_tags();
printf(
'<div class="wpforms-field-internal-information-row wpforms-field-internal-information-row-%s">%s</div>',
esc_attr( $label ),
wp_kses( $this->render_custom_preview( $label, $field, $args ), $allowed_tags )
);
}
/**
* Replace `[] some text` with checkboxes.
*
* Additionally, generates the input name by hashing the line of text where the checkbox is.
*
* @since 1.7.6
*
* @param string $description Expanded description.
* @param array $field Field data and settings.
*
* @return string
* @noinspection HtmlUnknownAttribute
*/
private function replace_checkboxes( string $description, array $field ): string {
if ( ! $this->form_id ) {
return $description;
}
$lines = explode( PHP_EOL, $description );
$replaced = [];
$post_meta = get_post_meta( $this->form_id, self::CHECKBOX_META_KEY, true );
$post_meta = ! empty( $post_meta ) ? (array) $post_meta : [];
$field_id = $field['id'] ?? 0;
$needle = '[] ';
foreach ( $lines as $line_number => $line ) {
$line = trim( $line );
if ( strpos( $line, $needle ) !== 0 ) {
$replaced[] = $line . PHP_EOL;
continue;
}
$field_name = sprintf( 'iif-%d-%s-%d', $field_id, md5( $line ), $line_number );
$checked = (int) isset( $post_meta[ $field_name ] );
$attributes = [
'name' => esc_attr( $field_name ),
'value' => 1,
];
if ( $this->is_editable() ) {
$attributes['disabled'] = 'disabled';
$attributes['title'] = esc_html__( 'This field is disabled in the editor mode.', 'wpforms-lite' );
}
$html = sprintf(
'<div class="wpforms-field-internal-information-checkbox-input"><input type="checkbox" %s %s /></div><div class="wpforms-field-internal-information-checkbox-label">',
wpforms_html_attributes(
'',
[ 'wpforms-field-internal-information-checkbox' ],
[],
$attributes
),
! $this->is_editable() ? checked( $checked, 1, false ) : ''
);
$line = substr_replace( $line, $html, 0, strlen( $needle ) );
$replaced[] = '<div class="wpforms-field-internal-information-checkbox-wrap">' . $line . '</div></div>';
}
return implode( '', $replaced );
}
/**
* Return allowed tags specific to internal information field content.
*
* @since 1.7.6
*
* @return array
*/
private function get_allowed_tags(): array {
$allowed_tags = wpforms_builder_preview_get_allowed_tags();
$allowed_tags['input'] = [
'type' => [],
'name' => [],
'value' => [],
'class' => [],
'checked' => [],
'disabled' => [],
'title' => [],
];
return $allowed_tags;
}
/**
* Adds link parameters to all links in the provided content.
*
* @since 1.8.3
*
* @param string $content The content to modify.
*
* @return string The modified content with UTM parameters added to links.
*/
private function add_link_attributes( string $content ): string {
if ( empty( $content ) || ! class_exists( 'DOMDocument' ) ) {
return $content;
}
$dom = new DOMDocument();
$form_obj = wpforms()->obj( 'form' );
$form_data = $form_obj ? $form_obj->get( $this->form_id, [ 'content_only' => true ] ) : [];
$templates_obj = wpforms()->obj( 'builder_templates' );
$template = $form_data['meta']['template'] ?? '';
$template_data = $templates_obj && $template ? $templates_obj->get_template( $template ) : [];
$template_name = $template_data['name'] ?? '';
$dom->loadHTML( htmlspecialchars_decode( htmlentities( $content ) ) );
$links = $dom->getElementsByTagName( 'a' );
foreach ( $links as $link ) {
$href = $link->getAttribute( 'href' );
$text = $link->textContent; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$modified_href = wpforms_utm_link( $href, 'Form Template Information Note', $template_name, $text );
$link->setAttribute( 'href', $modified_href );
$link->setAttribute( 'target', '_blank' );
$link->setAttribute( 'rel', 'noopener noreferrer' );
}
// Remove the wrapper elements.
$body = $dom->getElementsByTagName( 'body' )->item( 0 );
$child_nodes = $body->childNodes ?? []; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$inner_html = '';
foreach ( $child_nodes as $node ) {
$inner_html .= $dom->saveHTML( $node );
}
return $inner_html;
}
/**
* Add UTM parameters to the CTA button link.
*
* @since 1.7.6
*
* @param array $field Field data.
*
* @return string
*/
private function add_url_utm( array $field ): string {
$cta_link = (string) $field['cta-link'];
if ( strpos( $cta_link, 'https://wpforms.com' ) === 0 ) {
return wpforms_utm_link( $cta_link, 'Template Documentation' );
}
return $cta_link;
}
}
new WPForms_Field_Internal_Information();
@@ -0,0 +1,681 @@
<?php
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
/** @noinspection AutoloadingIssuesInspection */
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Name text field.
*
* @since 1.0.0
*/
class WPForms_Field_Name extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Name', 'wpforms-lite' );
$this->keywords = esc_html__( 'user, first, last', 'wpforms-lite' );
$this->type = 'name';
$this->icon = 'fa-user';
$this->order = 150;
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
private function hooks(): void {
// Define additional field properties.
add_filter( 'wpforms_field_properties_name', [ $this, 'field_properties' ], 5, 3 );
// Set field to default required.
add_filter( 'wpforms_field_new_required', [ $this, 'default_required' ], 10, 2 );
// This field requires fieldset+legend instead of the field label.
add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", [ $this, 'is_field_requires_fieldset' ], PHP_INT_MAX, 2 );
}
/**
* Define additional field properties.
*
* @since 1.3.7
*
* @param array|mixed $properties Field properties.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ): array { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded
$properties = (array) $properties;
$format = ! empty( $field['format'] ) ? esc_attr( $field['format'] ) : 'first-last';
// Simple format.
if ( $format === 'simple' ) {
$properties['inputs']['primary']['attr']['placeholder'] = ! empty( $field['simple_placeholder'] )
? $field['simple_placeholder'] :
'';
$properties['inputs']['primary']['attr']['value'] = ! empty( $field['simple_default'] )
? wpforms_process_smart_tags( $field['simple_default'], $form_data, [], '', 'field-properties' )
: '';
return $properties;
}
// Expanded formats.
// Remove primary for expanded formats since we have first, middle, last.
unset( $properties['inputs']['primary'] );
// Remove reference to an input element to prevent duplication.
if ( empty( $field['sublabel_hide'] ) ) {
unset( $properties['label']['attr']['for'] );
}
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$props = [
'inputs' => [
'first' => [
'attr' => [
'name' => "wpforms[fields][{$field_id}][first]",
'value' => ! empty( $field['first_default'] )
? wpforms_process_smart_tags( $field['first_default'], $form_data, [], '', 'field-properties' )
: '',
'placeholder' => ! empty( $field['first_placeholder'] ) ? $field['first_placeholder'] : '',
],
'block' => [
'wpforms-field-row-block',
'wpforms-first',
],
'class' => [
'wpforms-field-name-first',
],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
'required' => ! empty( $field['required'] ) ? 'required' : '',
'sublabel' => [
'hidden' => ! empty( $field['sublabel_hide'] ),
'value' => esc_html__( 'First', 'wpforms-lite' ),
],
],
'middle' => [
'attr' => [
'name' => "wpforms[fields][{$field_id}][middle]",
'value' => ! empty( $field['middle_default'] )
? wpforms_process_smart_tags( $field['middle_default'], $form_data, [], '', 'field-properties' )
: '',
'placeholder' => ! empty( $field['middle_placeholder'] ) ? $field['middle_placeholder'] : '',
],
'block' => [
'wpforms-field-row-block',
'wpforms-one-fifth',
],
'class' => [
'wpforms-field-name-middle',
],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}-middle",
'required' => '',
'sublabel' => [
'hidden' => ! empty( $field['sublabel_hide'] ),
'value' => esc_html__( 'Middle', 'wpforms-lite' ),
],
],
'last' => [
'attr' => [
'name' => "wpforms[fields][{$field_id}][last]",
'value' => ! empty( $field['last_default'] )
? wpforms_process_smart_tags( $field['last_default'], $form_data, [], '', 'field-properties' )
: '',
'placeholder' => ! empty( $field['last_placeholder'] ) ? $field['last_placeholder'] : '',
],
'block' => [
'wpforms-field-row-block',
],
'class' => [
'wpforms-field-name-last',
],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}-last",
'required' => ! empty( $field['required'] ) ? 'required' : '',
'sublabel' => [
'hidden' => ! empty( $field['sublabel_hide'] ),
'value' => esc_html__( 'Last', 'wpforms-lite' ),
],
],
],
];
$properties = array_merge_recursive( $properties, $props );
$has_common_error = ! empty( $properties['error']['value'] ) && is_string( $properties['error']['value'] );
// Input First: add error class if needed.
if ( ! empty( $properties['error']['value']['first'] ) || $has_common_error ) {
$properties['inputs']['first']['class'][] = 'wpforms-error';
}
// Input First: add required class if needed.
if ( ! empty( $field['required'] ) ) {
$properties['inputs']['first']['class'][] = 'wpforms-field-required';
}
// Input First: add column class.
$properties['inputs']['first']['block'][] = $format === 'first-last' ? 'wpforms-one-half' : 'wpforms-two-fifths';
// Input Middle: add error class if needed.
if ( $has_common_error ) {
$properties['inputs']['middle']['class'][] = 'wpforms-error';
}
// Input Last: add error class if needed.
if ( ! empty( $properties['error']['value']['last'] ) || $has_common_error ) {
$properties['inputs']['last']['class'][] = 'wpforms-error';
}
// Input Last: add required class if needed.
if ( ! empty( $field['required'] ) ) {
$properties['inputs']['last']['class'][] = 'wpforms-field-required';
}
// Input Last: add column class.
$properties['inputs']['last']['block'][] = $format === 'first-last' ? 'wpforms-one-half' : 'wpforms-two-fifths';
return $properties;
}
/**
* Name fields should default to being required.
*
* @since 1.0.8
*
* @param bool|mixed $required Whether the field is required.
* @param array $field Field data.
*
* @return bool
*/
public function default_required( $required, $field ): bool {
if ( $field['type'] === 'name' ) {
return true;
}
return (bool) $required;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field information.
*/
public function field_options( $field ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
// Define data.
$format = ! empty( $field['format'] ) ? esc_attr( $field['format'] ) : 'first-last';
/*
* Basic field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'basic-options', $field, $args );
// Label.
$this->field_option( 'label', $field );
// Format.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'format',
'value' => esc_html__( 'Format', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Select format to use for the name form field', 'wpforms-lite' ),
],
false
);
$fld = $this->field_element(
'select',
$field,
[
'slug' => 'format',
'value' => $format,
'options' => [
'simple' => esc_html__( 'Simple', 'wpforms-lite' ),
'first-last' => esc_html__( 'First Last', 'wpforms-lite' ),
'first-middle-last' => esc_html__( 'First Middle Last', 'wpforms-lite' ),
],
],
false
);
$args = [
'slug' => 'format',
'content' => $lbl . $fld,
];
$this->field_element( 'row', $field, $args );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'basic-options', $field, $args );
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
echo '<div class="format-selected-' . esc_attr( $format ) . ' format-selected">';
// Simple.
$simple_placeholder = ! empty( $field['simple_placeholder'] ) ? esc_attr( $field['simple_placeholder'] ) : '';
$simple_default = ! empty( $field['simple_default'] ) ? esc_attr( $field['simple_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-simple" id="wpforms-field-option-row-%d-simple" data-subfield="simple" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'simple_placeholder',
'value' => esc_html__( 'Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%d-simple_placeholder" name="fields[%d][simple_placeholder]" value="%s">', (int) $field['id'], (int) $field['id'], esc_attr( $simple_placeholder ) );
printf( '<label for="wpforms-field-option-%d-simple_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%d-simple_default" name="fields[%d][simple_default]" data-type="other" value="%s">', (int) $field['id'], (int) $field['id'], esc_attr( $simple_default ) );
printf( '<label for="wpforms-field-option-%d-simple_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
// First.
$first_placeholder = ! empty( $field['first_placeholder'] ) ? esc_attr( $field['first_placeholder'] ) : '';
$first_default = ! empty( $field['first_default'] ) ? esc_attr( $field['first_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-first" id="wpforms-field-option-row-%d-first" data-subfield="first-name" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'first_placeholder',
'value' => esc_html__( 'First Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'First name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%1$d-first_placeholder" name="fields[%1$d][first_placeholder]" value="%2$s">', (int) $field['id'], esc_attr( $first_placeholder ) );
printf( '<label for="wpforms-field-option-%d-first_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%1$d-first_default" name="fields[%1$d][first_default]" data-type="other" value="%2$s">', (int) $field['id'], esc_attr( $first_default ) );
printf( '<label for="wpforms-field-option-%d-first_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
// Middle.
$middle_placeholder = ! empty( $field['middle_placeholder'] ) ? esc_attr( $field['middle_placeholder'] ) : '';
$middle_default = ! empty( $field['middle_default'] ) ? esc_attr( $field['middle_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-middle" id="wpforms-field-option-row-%d-middle" data-subfield="middle-name" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'middle_placeholder',
'value' => esc_html__( 'Middle Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Middle name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%1$d-middle_placeholder" name="fields[%1$d][middle_placeholder]" value="%2$s">', (int) $field['id'], esc_attr( $middle_placeholder ) );
printf( '<label for="wpforms-field-option-%d-middle_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%1$d-middle_default" name="fields[%1$d][middle_default]" data-type="other" value="%2$s">', (int) $field['id'], esc_attr( $middle_default ) );
printf( '<label for="wpforms-field-option-%d-middle_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
// Last.
$last_placeholder = ! empty( $field['last_placeholder'] ) ? esc_attr( $field['last_placeholder'] ) : '';
$last_default = ! empty( $field['last_default'] ) ? esc_attr( $field['last_default'] ) : '';
printf( '<div class="wpforms-clear wpforms-field-option-row wpforms-field-option-row-last" id="wpforms-field-option-row-%d-last" data-subfield="last-name" data-field-id="%d">', esc_attr( $field['id'] ), esc_attr( $field['id'] ) );
$this->field_element(
'label',
$field,
[
'slug' => 'last_placeholder',
'value' => esc_html__( 'Last Name', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Last name field advanced options.', 'wpforms-lite' ),
]
);
echo '<div class="wpforms-field-options-columns-2 wpforms-field-options-columns">';
echo '<div class="placeholder wpforms-field-options-column">';
printf( '<input type="text" class="placeholder" id="wpforms-field-option-%1$d-last_placeholder" name="fields[%1$d][last_placeholder]" value="%2$s">', (int) $field['id'], esc_attr( $last_placeholder ) );
printf( '<label for="wpforms-field-option-%d-last_placeholder" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Placeholder', 'wpforms-lite' ) );
echo '</div>';
echo '<div class="default wpforms-field-options-column">';
printf( '<input type="text" class="default wpforms-smart-tags-enabled" id="wpforms-field-option-%1$d-last_default" name="fields[%1$d][last_default]" data-type="other" value="%2$s">', (int) $field['id'], esc_attr( $last_default ) );
printf( '<label for="wpforms-field-option-%d-last_default" class="sub-label">%s</label>', (int) $field['id'], esc_html__( 'Default Value', 'wpforms-lite' ) );
echo '</div>';
echo '</div>';
echo '</div>';
echo '</div>';
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide Label.
$this->field_option( 'label_hide', $field );
// Hide sublabels.
$sublabel_class = isset( $field['format'] ) && ! in_array( $field['format'], [ 'first-last', 'first-middle-last' ], true ) ? 'wpforms-hidden' : '';
$this->field_option( 'sublabel_hide', $field, [ 'class' => $sublabel_class ] );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'advanced-options', $field, $args );
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field information.
*/
public function field_preview( $field ) {
// Define data.
$simple_placeholder = ! empty( $field['simple_placeholder'] ) ? $field['simple_placeholder'] : '';
$first_placeholder = ! empty( $field['first_placeholder'] ) ? $field['first_placeholder'] : '';
$middle_placeholder = ! empty( $field['middle_placeholder'] ) ? $field['middle_placeholder'] : '';
$last_placeholder = ! empty( $field['last_placeholder'] ) ? $field['last_placeholder'] : '';
$simple_default = ! empty( $field['simple_default'] ) ? $field['simple_default'] : '';
$first_default = ! empty( $field['first_default'] ) ? $field['first_default'] : '';
$middle_default = ! empty( $field['middle_default'] ) ? $field['middle_default'] : '';
$last_default = ! empty( $field['last_default'] ) ? $field['last_default'] : '';
$format = ! empty( $field['format'] ) ? $field['format'] : 'first-last';
// Label.
$this->field_preview_option( 'label', $field );
?>
<div class="format-selected-<?php echo sanitize_html_class( $format ); ?> format-selected wpforms-clear">
<div class="wpforms-simple">
<input type="text" placeholder="<?php echo esc_attr( $simple_placeholder ); ?>" value="<?php echo esc_attr( $simple_default ); ?>" class="primary-input" readonly>
</div>
<div class="wpforms-first-name">
<input type="text" placeholder="<?php echo esc_attr( $first_placeholder ); ?>" value="<?php echo esc_attr( $first_default ); ?>" class="primary-input" readonly>
<label class="wpforms-sub-label"><?php esc_html_e( 'First', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-middle-name">
<input type="text" placeholder="<?php echo esc_attr( $middle_placeholder ); ?>" value="<?php echo esc_attr( $middle_default ); ?>" class="primary-input" readonly>
<label class="wpforms-sub-label"><?php esc_html_e( 'Middle', 'wpforms-lite' ); ?></label>
</div>
<div class="wpforms-last-name">
<input type="text" placeholder="<?php echo esc_attr( $last_placeholder ); ?>" value="<?php echo esc_attr( $last_default ); ?>" class="primary-input" readonly>
<label class="wpforms-sub-label"><?php esc_html_e( 'Last', 'wpforms-lite' ); ?></label>
</div>
</div>
<?php
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field information.
* @param array $deprecated Deprecated parameter, not used anymore.
* @param array $form_data Form data and settings.
*
* @noinspection HtmlUnknownAttribute
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$format = ! empty( $field['format'] ) ? esc_attr( $field['format'] ) : 'first-last';
$primary = ! empty( $field['properties']['inputs']['primary'] ) ? $field['properties']['inputs']['primary'] : '';
$first = ! empty( $field['properties']['inputs']['first'] ) ? $field['properties']['inputs']['first'] : '';
$middle = ! empty( $field['properties']['inputs']['middle'] ) ? $field['properties']['inputs']['middle'] : '';
$last = ! empty( $field['properties']['inputs']['last'] ) ? $field['properties']['inputs']['last'] : '';
// Simple format.
if ( $format === 'simple' ) {
// Primary field (Simple).
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
esc_attr( $primary['required'] )
);
// Expanded formats.
} else {
// Row wrapper.
echo '<div class="wpforms-field-row wpforms-field-' . sanitize_html_class( $field['size'] ) . '">';
// First name.
echo '<div ' . wpforms_html_attributes( false, $first['block'] ) . '>';
$this->field_display_sublabel( 'first', 'before', $field );
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $first['id'], $first['class'], $first['data'], $first['attr'] ),
esc_attr( $first['required'] )
);
$this->field_display_sublabel( 'first', 'after', $field );
$this->field_display_error( 'first', $field );
echo '</div>';
// Middle name.
if ( $format === 'first-middle-last' ) {
echo '<div ' . wpforms_html_attributes( false, $middle['block'] ) . '>';
$this->field_display_sublabel( 'middle', 'before', $field );
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $middle['id'], $middle['class'], $middle['data'], $middle['attr'] ),
esc_attr( $middle['required'] )
);
$this->field_display_sublabel( 'middle', 'after', $field );
$this->field_display_error( 'middle', $field );
echo '</div>';
}
// Last name.
echo '<div ' . wpforms_html_attributes( false, $last['block'] ) . '>';
$this->field_display_sublabel( 'last', 'before', $field );
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $last['id'], $last['class'], $last['data'], $last['attr'] ),
esc_attr( $last['required'] )
);
$this->field_display_sublabel( 'last', 'after', $field );
$this->field_display_error( 'last', $field );
echo '</div>';
echo '</div>';
}
}
/**
* Validate field on submitting a form.
*
* @since 1.0.0
*
* @param int $field_id Field id.
* @param array|string $field_submit Submitted field value (raw data).
* @param array $form_data Form data.
*/
public function validate( $field_id, $field_submit, $form_data ) {
if ( empty( $form_data['fields'][ $field_id ]['required'] ) ) {
return;
}
// Extended validation needed for the different name fields.
$form_id = $form_data['id'];
$format = $form_data['fields'][ $field_id ]['format'];
$required = wpforms_get_required_label();
$process = wpforms()->obj( 'process' );
if ( $format === 'simple' && wpforms_is_empty_string( $field_submit ) ) {
$process->errors[ $form_id ][ $field_id ] = $required;
return;
}
if ( ! ( $format === 'first-last' || $format === 'first-middle-last' ) ) {
return;
}
$this->validate_complicated_formats( $process, $form_id, $field_id, $field_submit, $required );
}
/**
* Format and sanitize field.
*
* @since 1.0.0
*
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
// Define data.
$name = isset( $form_data['fields'][ $field_id ]['label'] ) && ! wpforms_is_empty_string( $form_data['fields'][ $field_id ]['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
$first = isset( $field_submit['first'] ) && ! wpforms_is_empty_string( $field_submit['first'] ) ? $field_submit['first'] : '';
$middle = isset( $field_submit['middle'] ) && ! wpforms_is_empty_string( $field_submit['middle'] ) ? $field_submit['middle'] : '';
$last = isset( $field_submit['last'] ) && ! wpforms_is_empty_string( $field_submit['last'] ) ? $field_submit['last'] : '';
if ( is_array( $field_submit ) ) {
$value = implode( ' ', array_filter( [ $first, $middle, $last ] ) );
} else {
$value = $field_submit;
}
// Set final field details.
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => sanitize_text_field( $value ),
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
'first' => sanitize_text_field( $first ),
'middle' => sanitize_text_field( $middle ),
'last' => sanitize_text_field( $last ),
];
}
/**
* Determine if the field requires fieldset+legend instead of the regular field label.
*
* @since 1.8.1
*
* @param bool $requires_fieldset True if it requires fieldset.
* @param array $field Field data.
*
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function is_field_requires_fieldset( $requires_fieldset, $field ) {
return isset( $field['format'] ) && $field['format'] !== 'simple';
}
/**
* Validate complicated formats.
*
* @since 1.8.2.3
*
* @param WPForms_Process $process Process class instance.
* @param int|string $form_id Form id.
* @param int|string $field_id Field id.
* @param array $field_submit Field submit.
* @param string $required Required message text.
*/
private function validate_complicated_formats( $process, $form_id, $field_id, $field_submit, $required ) {
// Prevent PHP Warning: Illegal string offset first or 'last'.
if ( isset( $process->errors[ $form_id ][ $field_id ] ) ) {
$process->errors[ $form_id ][ $field_id ] = (array) $process->errors[ $form_id ][ $field_id ];
}
if ( isset( $field_submit['first'] ) && wpforms_is_empty_string( $field_submit['first'] ) ) {
$process->errors[ $form_id ][ $field_id ]['first'] = $required;
}
if ( isset( $field_submit['last'] ) && wpforms_is_empty_string( $field_submit['last'] ) ) {
$process->errors[ $form_id ][ $field_id ]['last'] = $required;
}
}
}
new WPForms_Field_Name();
@@ -0,0 +1,452 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Forms\Fields\Traits\NumberField as NumberFieldTrait;
/**
* Number Slider field.
*
* @since 1.5.7
*/
class WPForms_Field_Number_Slider extends WPForms_Field {
use NumberFieldTrait;
/**
* Default minimum value of the field.
*
* @since 1.5.7
*/
const SLIDER_MIN = 0;
/**
* Default maximum value of the field.
*
* @since 1.5.7
*/
const SLIDER_MAX = 10;
/**
* Default step value of the field.
*
* @since 1.5.7
*/
const SLIDER_STEP = 1;
/**
* Primary class constructor.
*
* @since 1.5.7
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Number Slider', 'wpforms-lite' );
$this->type = 'number-slider';
$this->icon = 'fa-sliders';
$this->order = 180;
// Customize value format for HTML emails.
add_filter( 'wpforms_html_field_value', [ $this, 'html_email_value' ], 10, 4 );
// Builder strings.
add_filter( 'wpforms_builder_strings', [ $this, 'add_builder_strings' ] );
}
/**
* Add Builder strings.
*
* @since 1.6.2.3
*
* @param array $strings Form Builder strings.
*
* @return array Form Builder strings.
*/
public function add_builder_strings( $strings ) {
$strings['error_number_slider_increment'] = esc_html__( 'Increment value should be greater than zero. Decimal fractions allowed.', 'wpforms-lite' );
return $strings;
}
/**
* Customize format for HTML email notifications.
*
* @since 1.5.7
*
* @param string $val Field value.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
* @param string $context Value display context.
*
* @return string
*/
public function html_email_value( $val, $field, $form_data = [], $context = '' ) {
if ( empty( $field['value_raw'] ) || $field['type'] !== $this->type ) {
return $val;
}
$value = isset( $field['value_raw']['value'] ) ? (float) $field['value_raw']['value'] : 0;
$min = isset( $field['value_raw']['min'] ) ? (float) $field['value_raw']['min'] : self::SLIDER_MIN;
$max = isset( $field['value_raw']['max'] ) ? (float) $field['value_raw']['max'] : self::SLIDER_MAX;
$html_value = $value;
if ( strpos( $field['value_raw']['value_display'], '{value}' ) !== false ) {
$html_value = str_replace(
'{value}',
/* translators: %1$s - Number slider selected value, %2$s - its minimum value, %3$s - its maximum value. */
sprintf( esc_html__( '%1$s (%2$s min / %3$s max)', 'wpforms-lite' ), $value, $min, $max ),
$field['value_raw']['value_display']
);
}
return $html_value;
}
/**
* Field options panel inside the builder.
*
* @since 1.5.7
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Set default values for Min, Max, Step, Default Value Options.
$field = $this->set_default_field_args( $field );
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'basic-options', $field, $args );
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle disabled.
$this->field_element(
'text',
$field,
[
'slug' => 'required',
'value' => '',
'type' => 'hidden',
]
);
// Min/Max.
$min_max_args = [
'class' => 'wpforms-number-slider',
'label' => esc_html__( 'Value Range', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Define the minimum and the maximum values for the slider.', 'wpforms-lite' ),
];
$min_max = $this->field_number_option_min_max( $field, $min_max_args, false );
// Default value.
$default_value_args = [
'class' => 'wpforms-number-slider-default-value',
];
$default_value = $this->field_number_option_default_value( $field, $default_value_args, false );
// Increment.
$step_args = [
'class' => 'wpforms-number-slider-step',
'tooltip' => esc_html__( 'Determines the increment between selectable values on the slider.', 'wpforms-lite' ),
];
$step = $this->field_number_option_step( $field, $step_args, false );
// Print of options markup: Minimum, Maximum, Increment, Default Value.
$this->field_element(
'row',
$field,
[
'slug' => 'number_min_max_step_dependent',
'content' => $min_max . $default_value . $step,
'class' => 'wpforms-field-number-slider-option',
],
true
);
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'basic-options', $field, $args );
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
// Value display.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'value_display',
'value' => esc_html__( 'Value Display', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Displays the currently selected value below the slider.', 'wpforms-lite' ),
],
false
);
$fld = $this->field_element(
'text',
$field,
[
'slug' => 'value_display',
'class' => 'wpforms-number-slider-value-display',
'value' => isset( $field['value_display'] ) ? $field['value_display'] : $this->get_default_display_value(),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'value_display',
'content' => $lbl . $fld,
]
);
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'advanced-options', $field, $args );
}
/**
* Get default display value.
*
* @since 1.7.1
*
* @return string
*/
private function get_default_display_value() {
return sprintf( /* translators: %s - value. */
esc_html__( 'Selected Value: %s', 'wpforms-lite' ),
'{value}'
);
}
/**
* Field preview inside the builder.
*
* @since 1.5.7
*
* @param array $field Field data.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
$value_display = isset( $field['value_display'] ) ? esc_attr( $field['value_display'] ) : $this->get_default_display_value();
$default_value = ! empty( $field['default_value'] ) ? (float) $field['default_value'] : 0;
echo wpforms_render( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'fields/number-slider/builder-preview',
[
'min' => isset( $field['min'] ) && is_numeric( $field['min'] ) ? (float) $field['min'] : self::SLIDER_MIN,
'max' => isset( $field['max'] ) && is_numeric( $field['max'] ) ? (float) $field['max'] : self::SLIDER_MAX,
'step' => isset( $field['step'] ) && is_numeric( $field['step'] ) ? (float) $field['step'] : self::SLIDER_STEP,
'value_display' => $value_display,
'default_value' => $default_value,
'value_hint' => str_replace( '{value}', '<b>' . $default_value . '</b>', wp_kses( $value_display, wpforms_builder_preview_get_allowed_tags() ) ),
'field_id' => $field['id'],
],
true
);
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.5.7
*
* @param array $field Field data and settings.
* @param array $deprecated Deprecated field attributes. Use $field['properties'] instead.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
$value_display = isset( $field['value_display'] ) ? esc_attr( $field['value_display'] ) : esc_html__( 'Selected Value: {value}', 'wpforms-lite' );
$hint_value = ! empty( $primary['attr']['value'] ) ? (float) $primary['attr']['value'] : 0;
$hint = str_replace( '{value}', '<b>' . $hint_value . '</b>', $value_display );
// phpcs:ignore
echo wpforms_render(
'fields/number-slider/frontend',
[
'atts' => $primary['attr'],
'class' => $primary['class'],
'datas' => $primary['data'],
'id' => $primary['id'],
'max' => isset( $field['max'] ) && is_numeric( $field['max'] ) ? (float) $field['max'] : self::SLIDER_MAX,
'min' => isset( $field['min'] ) && is_numeric( $field['min'] ) ? (float) $field['min'] : self::SLIDER_MIN,
'required' => $primary['required'],
'step' => isset( $field['step'] ) && is_numeric( $field['step'] ) ? (float) $field['step'] : self::SLIDER_STEP,
'value_display' => $value_display,
'value_hint' => $hint,
],
true
);
}
/**
* Validate field on form submit.
*
* @since 1.5.7
*
* @param int $field_id Field ID.
* @param int|float|string $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$form_id = $form_data['id'];
$field_submit = (float) $this->sanitize_value( $field_submit );
// Basic required check - if field is marked as required, check for entry data.
if (
! empty( $form_data['fields'][ $field_id ]['required'] ) &&
empty( $field_submit ) &&
(string) $field_submit !== '0'
) {
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = wpforms_get_required_label();
}
// Check if value is numeric.
if ( ! empty( $field_submit ) && ! is_numeric( $field_submit ) ) {
/**
* Filter the error message for the number field.
*
* @since 1.0.0
*
* @param string $message Error message.
*/
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = apply_filters( 'wpforms_valid_number_label', esc_html__( 'Please provide a valid value.', 'wpforms-lite' ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
/**
* Format and sanitize field.
*
* @since 1.5.7
*
* @param int $field_id Field ID.
* @param int|string|float $field_submit Submitted field value.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
// Define data.
$name = ! empty( $form_data['fields'][ $field_id ]['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
$value = (float) $this->sanitize_value( $field_submit );
$value_raw = [
'value' => $value,
'min' => (float) $form_data['fields'][ $field_id ]['min'],
'max' => (float) $form_data['fields'][ $field_id ]['max'],
'value_display' => wp_kses_post( $form_data['fields'][ $field_id ]['value_display'] ),
];
// Set final field details.
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => $value,
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Sanitize the value.
*
* @since 1.5.7
*
* @param string $value The number field submitted value.
*
* @return float|int|string
*/
private function sanitize_value( $value ) {
// Some browsers allow other non-digit/decimal characters to be submitted
// with the num input, which then trips the is_numeric validation below.
// To get around this we remove all chars that are not expected.
$signed_value = preg_replace( '/[^-0-9.]/', '', $value );
// If there's no number on the signed value we return zero.
// We have to do that because since PHP 8.0, the abs() function is allowed an argument with int|float type.
if ( ! is_numeric( $signed_value ) ) {
return 0;
}
$abs_value = abs( $signed_value );
$value = strpos( $signed_value, '-' ) === 0 ? '-' . $abs_value : $abs_value;
return $value;
}
/**
* Sets default field settings.
*
* @since 1.9.4
*
* @param array $field Field settings.
*
* @return array Modified array.
*/
private function set_default_field_args( $field ) {
$field['min'] = empty( $field['min'] ) ? self::SLIDER_MIN : $field['min'];
$field['max'] = empty( $field['max'] ) ? self::SLIDER_MAX : $field['max'];
$field['step'] = empty( $field['step'] ) ? self::SLIDER_STEP : $field['step'];
$field['default_value'] = empty( $field['default_value'] ) ? self::SLIDER_MIN : $field['default_value'];
return $field;
}
}
new WPForms_Field_Number_Slider();
@@ -0,0 +1,278 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use WPForms\Forms\Fields\Traits\NumberField as NumberFieldTrait;
/**
* Number text field.
*
* @since 1.0.0
*/
class WPForms_Field_Number extends WPForms_Field {
use NumberFieldTrait;
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Numbers', 'wpforms-lite' );
$this->type = 'number';
$this->icon = 'fa-hashtag';
$this->order = 130;
$this->hooks();
$this->number_hooks();
}
/**
* Hooks.
*
* @since 1.9.4
*/
private function hooks() {
// Define additional field properties.
add_filter( 'wpforms_field_properties_number', [ $this, 'field_properties' ], 5, 3 );
}
/**
* Define additional field properties.
*
* @since 1.9.4
*
* @param array|mixed $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
*/
public function field_properties( $properties, $field, $form_data ): array {
$properties = (array) $properties;
if ( is_numeric( $field['min'] ?? null ) ) {
$properties['inputs']['primary']['attr']['min'] = (float) $field['min'];
}
if ( is_numeric( $field['max'] ?? null ) ) {
$properties['inputs']['primary']['attr']['max'] = (float) $field['max'];
}
$properties['inputs']['primary']['attr']['step'] = 'any';
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'basic-options', $field, $args );
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'basic-options', $field, $args );
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Min/Max.
$this->field_number_option_min_max( $field, [ 'class' => 'wpforms-numbers' ] );
// Default value.
$this->field_option( 'default_value', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$args = [
'markup' => 'close',
];
$this->field_option( 'advanced-options', $field, $args );
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data.
*/
public function field_preview( $field ) {
// Define data.
$placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
// Label.
$this->field_preview_option( 'label', $field );
// Primary input.
echo '<input type="text" placeholder="' . esc_attr( $placeholder ) . '" value="' . esc_attr( $default_value ) . '" class="primary-input" readonly>';
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field data.
* @param array $deprecated Deprecated, not used.
* @param array $form_data Form data.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
// Primary field.
printf(
'<input type="number" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
esc_attr( $primary['required'] )
);
}
/**
* Validate field on form submit.
*
* @since 1.0.0
*
* @param int $field_id Field id.
* @param string $field_submit Submitted field value (raw data).
* @param array $form_data Form data.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$form_id = $form_data['id'];
$value = $this->sanitize_value( $field_submit );
// If field is marked as required, check for entry data.
if (
! empty( $form_data['fields'][ $field_id ]['required'] ) &&
empty( $value ) &&
! is_numeric( $value )
) {
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = wpforms_get_required_label();
}
// Check if value is numeric.
if ( ! empty( $value ) && ! is_numeric( $value ) ) {
/**
* Filter the error message for the number field.
*
* @since 1.0.0
*
* @param string $message Error message.
*/
wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] = apply_filters( 'wpforms_valid_number_label', esc_html__( 'Please enter a valid number.', 'wpforms-lite' ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
}
/**
* Format and sanitize field.
*
* @since 1.3.5
*
* @param int $field_id Field id.
* @param string $field_submit Submitted value.
* @param array $form_data Form data.
*/
public function format( $field_id, $field_submit, $form_data ) {
// Define data.
$name = ! empty( $form_data['fields'][ $field_id ]['label'] ) ? $form_data['fields'][ $field_id ]['label'] : '';
// Set final field details.
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => sanitize_text_field( $name ),
'value' => $this->sanitize_value( $field_submit ),
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Sanitize the value.
*
* @since 1.5.7
*
* @param string $value The number field submitted value.
*
* @return float|int|string
*/
private function sanitize_value( $value ) {
if ( empty( $value ) && ! is_numeric( $value ) ) {
return '';
}
// Some browsers allow other non-digit/decimal characters to be submitted
// with the num input, which then trips the is_numeric validation below.
// To get around this we remove all chars that are not expected.
$signed_value = preg_replace( '/[^-0-9.]/', '', $value );
$abs_value = str_replace( '-', '', $signed_value );
return $signed_value < 0 ? '-' . $abs_value : $abs_value;
}
}
new WPForms_Field_Number();
@@ -0,0 +1,944 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Multiple Choice field.
*
* @since 1.0.0
*/
class WPForms_Field_Radio extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Multiple Choice', 'wpforms-lite' );
$this->keywords = esc_html__( 'radio', 'wpforms-lite' );
$this->type = 'radio';
$this->icon = 'fa-dot-circle-o';
$this->order = 110;
$this->defaults = [
1 => [
'label' => esc_html__( 'First Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
2 => [
'label' => esc_html__( 'Second Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
3 => [
'label' => esc_html__( 'Third Choice', 'wpforms-lite' ),
'value' => '',
'image' => '',
'icon' => '',
'icon_style' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
$this->hooks();
}
/**
* Hooks.
*
* @since 1.8.1
*/
private function hooks() {
// Customize HTML field values.
add_filter( 'wpforms_html_field_value', [ $this, 'field_html_value' ], 10, 4 );
add_filter( "wpforms_{$this->type}_field_html_value_images", [ $this, 'field_html_value_images' ], 10, 3 );
// Define additional field properties.
add_filter( 'wpforms_field_properties_radio', [ $this, 'field_properties' ], 5, 3 );
// This field requires fieldset+legend instead of the field label.
add_filter( "wpforms_frontend_modern_is_field_requires_fieldset_{$this->type}", '__return_true', PHP_INT_MAX, 2 );
// Load assets.
add_action( 'wpforms_builder_enqueues', [ $this, 'builder_assets' ] );
// Modify an export data format for the Other option.
add_filter( 'wpforms_pro_admin_entries_export_ajax_get_entry_fields_data_field', [ $this, 'export_entry_field_data' ] );
// Allow radio fields to be included in the Keyword Filter.
add_filter( 'wpforms_pro_anti_spam_keyword_filter_get_filtered_fields', [ $this, 'add_field_to_anti_spam_keyword_filter' ] );
// Adjust entry field before saving to entry_fields DB table.
add_filter( 'wpforms_entry_save_fields', [ $this, 'save_field' ], 10, 3 );
}
/**
* Enqueue assets for the builder.
*
* @since 1.9.8.3
*/
public function builder_assets() {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-multiple-choices',
WPFORMS_PLUGIN_URL . "assets/js/admin/builder/multiple-choices{$min}.js",
[ 'jquery', 'wpforms-builder' ],
WPFORMS_VERSION,
false
);
}
/**
* Adjust builder preview container classes.
*
* Adds size-{small|medium|large} to the Radio field container in the Builder
* when the "Add Other Choice" option is enabled.
*
* @since 1.9.8.3
*
* @param string $css Existing class string.
* @param array $field Field data and settings.
*
* @return string
*/
public function preview_field_class( $css, $field ): string {
$css = parent::preview_field_class( $css, $field );
if ( $field['type'] !== $this->type ) {
return $css;
}
// Apply a size class to the field container when Other Choice is enabled.
if ( $this->has_other_choice( $field ) ) {
$size = ! empty( $field['other_size'] ) ? sanitize_html_class( $field['other_size'] ) : 'medium';
$css .= ' size-' . $size;
}
return $css;
}
/**
* Define additional field properties.
*
* @since 1.4.5
*
* @param array $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ) {
// Remove primary input, unset for attribute for label.
unset( $properties['inputs']['primary'], $properties['label']['attr']['for'] );
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$choices = $field['choices'];
$dynamic = wpforms_get_field_dynamic_choices( $field, $form_id, $form_data );
if ( $dynamic !== false ) {
$choices = $dynamic;
$field['show_values'] = true;
}
// Set input container (ul) properties.
$properties['input_container'] = [
'class' => [ ! empty( $field['random'] ) ? 'wpforms-randomize' : '' ],
'data' => [],
'attr' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
];
// Set input properties.
foreach ( $choices as $key => $choice ) {
// Used for dynamic choices.
$depth = isset( $choice['depth'] ) ? absint( $choice['depth'] ) : 1;
$value = ! empty( $field['show_values'] ) ? $choice['value'] : $choice['label'];
/* translators: %s - choice number. */
$value = ( $value === '' ) ? sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key ) : $value;
// Check if this is the "Other" choice.
$is_other_choice = isset( $choice['other'] ) && (bool) $choice['other'] === true;
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}", "depth-{$depth}", $is_other_choice ? 'wpforms-other-choice' : '' ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $choice['label'],
],
'attr' => [
'name' => "wpforms[fields][{$field_id}]",
'value' => $value,
],
'class' => [],
'data' => $is_other_choice ? [ 'other-choice' => 'true' ] : [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'icon' => isset( $choice['icon'] ) ? $choice['icon'] : '',
'icon_style' => isset( $choice['icon_style'] ) ? $choice['icon_style'] : '',
'image' => isset( $choice['image'] ) ? $choice['image'] : '',
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => isset( $choice['default'] ),
];
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
// Custom properties if image choices is enabled.
if ( ! $dynamic && ! empty( $field['choices_images'] ) ) {
$properties['input_container']['class'][] = 'wpforms-image-choices';
$properties['input_container']['class'][] = 'wpforms-image-choices-' . sanitize_html_class( $field['choices_images_style'] );
foreach ( $properties['inputs'] as $key => $inputs ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-image-choices-item';
if ( in_array( $field['choices_images_style'], [ 'modern', 'classic' ], true ) ) {
$properties['inputs'][ $key ]['class'][] = 'wpforms-screen-reader-element';
}
}
} elseif ( ! $dynamic && ! empty( $field['choices_icons'] ) ) {
$properties = wpforms()->obj( 'icon_choices' )->field_properties( $properties, $field );
}
// Add selected class for choices with defaults.
foreach ( $properties['inputs'] as $key => $inputs ) {
if ( ! empty( $inputs['default'] ) ) {
$properties['inputs'][ $key ]['container']['class'][] = 'wpforms-selected';
}
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option( 'choices', $field );
// AI Feature.
$this->field_option(
'ai_modal_button',
$field,
[
'value' => esc_html__( 'Generate Choices', 'wpforms-lite' ),
'type' => 'choices',
]
);
// Add Other Choice.
$this->field_option( 'choices_other', $field );
// Other Field Size for "Other" input.
$this->field_option( 'other_size', $field );
// Other Placeholder for "Other" input.
$this->field_option( 'other_placeholder', $field );
// Choices Images.
$this->field_option( 'choices_images', $field );
// Hide Choices Images.
$this->field_option( 'choices_images_hide', $field );
// Choices Images Style (theme).
$this->field_option( 'choices_images_style', $field );
// Choices Icons.
$this->field_option( 'choices_icons', $field );
// Choices Icons Color.
$this->field_option( 'choices_icons_color', $field );
// Choices Icons Size.
$this->field_option( 'choices_icons_size', $field );
// Choices Icons Style.
$this->field_option( 'choices_icons_style', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Randomize order of choices.
$this->field_element(
'row',
$field,
[
'slug' => 'random',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'random',
'value' => isset( $field['random'] ) ? '1' : '0',
'desc' => esc_html__( 'Randomize Choices', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to randomize the order of the choices.', 'wpforms-lite' ),
],
false
),
]
);
// Show Values toggle option. This option will only show if already used
// or if manually enabled by a filter.
if ( ! empty( $field['show_values'] ) || wpforms_show_fields_options_setting() ) {
$this->field_element(
'row',
$field,
[
'slug' => 'show_values',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'show_values',
'value' => isset( $field['show_values'] ) ? $field['show_values'] : '0',
'desc' => esc_html__( 'Show Values', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to manually set form field values.', 'wpforms-lite' ),
],
false
),
]
);
}
// Display format.
$this->field_option( 'input_columns', $field );
// Dynamic choice auto-populating toggle.
$this->field_option( 'dynamic_choices', $field );
// Dynamic choice source.
$this->field_option( 'dynamic_choices_source', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Choices.
$this->field_preview_option( 'choices', $field );
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end and admin entry edit page.
*
* @since 1.0.0
*
* @param array $field Field settings.
* @param array $deprecated Deprecated array.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
$using_image_choices = empty( $field['dynamic_choices'] ) && empty( $field['choices_icons'] ) && ! empty( $field['choices_images'] );
$using_icon_choices = empty( $field['dynamic_choices'] ) && empty( $field['choices_images'] ) && ! empty( $field['choices_icons'] );
// Define data.
$container = $field['properties']['input_container'];
$choices = $field['properties']['inputs'];
// Do not display the field with empty choices on the frontend.
if ( ! $choices && ! is_admin() ) {
return;
}
// Display a warning message on Entry Edit page.
if ( ! $choices && is_admin() ) {
$this->display_empty_dynamic_choices_message( $field );
return;
}
$amp_state_id = '';
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$amp_state_id = str_replace( '-', '_', sanitize_key( $container['id'] ) ) . '_state';
$state = [
'selected' => null,
];
foreach ( $choices as $key => $choice ) {
if ( $choice['default'] ) {
$state['selected'] = $choice['attr']['value'];
break;
}
}
printf(
'<amp-state id="%s"><script type="application/json">%s</script></amp-state>',
esc_attr( $amp_state_id ),
wp_json_encode( $state )
);
}
printf(
'<ul %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
foreach ( $choices as $key => $choice ) {
$label = $this->get_choices_label( $choice['label']['text'] ?? '', $key, $field );
if ( wpforms_is_amp() && ( $using_image_choices || $using_icon_choices ) ) {
$choice['container']['attr']['[class]'] = sprintf(
'%s + ( %s == %s ? " wpforms-selected" : "")',
wp_json_encode( implode( ' ', $choice['container']['class'] ) ),
$amp_state_id,
wp_json_encode( $choice['attr']['value'] )
);
}
printf(
'<li %s>',
wpforms_html_attributes( $choice['container']['id'], $choice['container']['class'], $choice['container']['data'], $choice['container']['attr'] )
);
if ( $using_image_choices ) {
// Make sure the image choices are keyboard-accessible.
$choice['label']['attr']['tabindex'] = 0;
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState(%s)',
wp_json_encode( [ $amp_state_id => $choice['attr']['value'] ] )
);
$choice['label']['attr']['role'] = 'button';
}
if ( is_array( $choice['label']['class'] ) && wpforms_is_empty_string( $label ) ) {
$choice['label']['class'][] = 'wpforms-field-label-inline-empty';
}
// Image choices.
printf(
'<label %s>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] )
);
echo '<span class="wpforms-image-choices-image">';
if ( ! empty( $choice['image'] ) ) {
printf(
'<img src="%s" alt="%s"%s>',
esc_url( $choice['image'] ),
esc_attr( $label ),
! empty( $label ) ? ' title="' . esc_attr( $label ) . '"' : ''
);
}
echo '</span>';
if ( $field['choices_images_style'] === 'none' ) {
echo '<br>';
}
$choice['attr']['tabindex'] = '-1';
if ( wpforms_is_amp() ) {
$choice['attr']['[checked]'] = sprintf(
'%s == %s',
$amp_state_id,
wp_json_encode( $choice['attr']['value'] )
);
}
printf(
'<input type="radio" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $choice['required'] ),
checked( '1', $choice['default'], false )
);
echo '<span class="wpforms-image-choices-label">' . wp_kses_post( $choice['label']['text'] ) . '</span>';
echo '</label>';
} elseif ( $using_icon_choices ) {
if ( wpforms_is_amp() ) {
$choice['label']['attr']['on'] = sprintf(
'tap:AMP.setState(%s)',
wp_json_encode( [ $amp_state_id => $choice['attr']['value'] ] )
);
$choice['label']['attr']['role'] = 'button';
}
// Icon Choices.
wpforms()->obj( 'icon_choices' )->field_display( $field, $choice, 'radio' );
} else {
// Normal display.
printf(
'<input type="radio" %s %s %s>',
wpforms_html_attributes( $choice['id'], $choice['class'], $choice['data'], $choice['attr'] ),
esc_attr( $choice['required'] ),
checked( '1', $choice['default'], false )
);
printf(
'<label %s>%s</label>',
wpforms_html_attributes( $choice['label']['id'], $choice['label']['class'], $choice['label']['data'], $choice['label']['attr'] ),
wp_kses_post( $label )
);
}
// Capture the text field for "Other" choice to render separately below the list.
if ( ! empty( $choice['data']['other-choice'] ) ) {
$default_value = '';
$size = ! empty( $field['other_size'] ) ? sanitize_html_class( $field['other_size'] ) : 'medium';
$size_class = 'wpforms-field-' . $size;
// Do not hide the Other input if this choice is set as default.
$hidden_class = ! empty( $choice['default'] ) ? '' : ' wpforms-hidden';
if ( isset( $field['choices'][ $key ]['value'] ) && $field['choices'][ $key ]['value'] !== '' ) {
$default_value = $field['choices'][ $key ]['value'];
}
/**
* Filters the default value of the Other choice field option.
*
* This filter allows modifying what value should be prefilled for the Other choice inputs.
*
* @since 1.9.8.3
*
* @param string $default_value Default value for the "Other" choice input.
* @param array $field Field data and settings.
* @param string $label Field label.
*/
$default_value = apply_filters( 'wpforms_field_radio_other_choice_default_value', $default_value, $field, $label );
$other_atts = [
'name' => "wpforms[fields][{$field['id']}][other]",
'value' => $default_value,
];
if ( empty( $choice['default'] ) ) {
$other_atts['disabled'] = 'disabled';
}
if ( ! empty( $field['other_placeholder'] ) ) {
$other_atts['placeholder'] = $field['other_placeholder'];
}
$other_input_html = sprintf(
'<input type="text" %s required>',
wpforms_html_attributes(
"wpforms-{$form_data['id']}-field_{$field['id']}_other",
[ 'wpforms-other-input', 'wpforms-field-required', $size_class, $hidden_class ],
[],
$other_atts
)
);
}
echo '</li>';
}
echo '</ul>';
// Render the captured "Other" input separately, under the list of options.
if ( ! empty( $other_input_html ) ) {
echo $other_input_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Validate field.
*
* @since 1.8.2
*
* @param int $field_id Field ID.
* @param string|array $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
// Skip validation if field is dynamic and choices are empty.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
return;
}
parent::validate( $field_id, $field_submit, $form_data );
}
/**
* Format and sanitize field.
*
* @since 1.0.2
* @since 1.9.8.3 Changed the expected $field_submit from string to mixed as in case with the Other option we can expect the array to arrive here.
*
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted form data.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
$dynamic = ! empty( $field['dynamic_choices'] ) ? $field['dynamic_choices'] : false;
$name = sanitize_text_field( $field['label'] );
$value_raw = sanitize_text_field( $field_submit );
$data = [
'name' => $name,
'value' => '',
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
if ( 'post_type' === $dynamic && ! empty( $field['dynamic_post_type'] ) ) {
// Dynamic population is enabled using post type.
$data['dynamic'] = 'post_type';
$data['dynamic_items'] = absint( $value_raw );
$data['dynamic_post_type'] = $field['dynamic_post_type'];
$post = get_post( $value_raw );
if ( ! empty( $post ) && ! is_wp_error( $post ) && $data['dynamic_post_type'] === $post->post_type ) {
$data['value'] = esc_html( wpforms_get_post_title( $post ) );
}
} elseif ( 'taxonomy' === $dynamic && ! empty( $field['dynamic_taxonomy'] ) ) {
// Dynamic population is enabled using taxonomy.
$data['dynamic'] = 'taxonomy';
$data['dynamic_items'] = absint( $value_raw );
$data['dynamic_taxonomy'] = $field['dynamic_taxonomy'];
$term = get_term( $value_raw, $data['dynamic_taxonomy'] );
if ( ! empty( $term ) && ! is_wp_error( $term ) ) {
$data['value'] = esc_html( wpforms_get_term_name( $term ) );
}
} else {
// Normal processing, dynamic population is off.
$choice_key = '';
// If show_values is true, that means value posted is the raw value
// and not the label. So we need to set label value. Also store
// the choice key.
if ( ! empty( $field['show_values'] ) ) {
foreach ( $field['choices'] as $key => $choice ) {
if ( ! empty( $field_submit ) && $choice['value'] === $field_submit ) {
$data['value'] = sanitize_text_field( $choice['label'] );
$choice_key = $key;
break;
}
}
} else {
$data['value'] = $value_raw;
// Determine choice key, this is needed for image choices.
foreach ( $field['choices'] as $key => $choice ) {
/* translators: %s - choice number. */
if ( $field_submit === $choice['label'] || $value_raw === sprintf( esc_html__( 'Choice %s', 'wpforms-lite' ), $key ) ) {
$choice_key = $key;
break;
}
}
}
// Images choices are enabled, lookup and store image URL.
if ( ! empty( $choice_key ) && ! empty( $field['choices_images'] ) ) {
$data['image'] = ! empty( $field['choices'][ $choice_key ]['image'] ) ? esc_url_raw( $field['choices'][ $choice_key ]['image'] ) : '';
}
}
// For the Other option the value_raw is the option and the value is text from the input.
if ( is_array( $field_submit ) && ! empty( $field_submit['other'] ) ) {
$data['value'] = sanitize_text_field( $field_submit['other'] );
// Save the flag that the saved value is from the other option field.
$data['is_other'] = true;
foreach ( $field['choices'] as $choice ) {
if ( isset( $choice['other'] ) ) {
$data['value_raw'] = $choice['label'];
$data['image'] = ! empty( $field['choices_images'] ) && ! empty( $choice['image'] ) ? esc_url_raw( $choice['image'] ) : '';
break;
}
}
}
// Push field details to be saved.
wpforms()->obj( 'process' )->fields[ $field_id ] = $data;
}
/**
* Export entry field data.
*
* @since 1.9.8.3
*
* @param array|mixed $field Field data.
*
* @return array
*/
public function export_entry_field_data( $field ): array {
$field = (array) $field;
if ( empty( $field['is_other'] ) ) {
return $field;
}
$value = (string) ( $field['value'] ?? '' );
$value_raw = (string) ( $field['value_raw'] ?? '' );
$field['value'] = $value_raw . ': ' . $value;
return $field;
}
/**
* Include a radio field in allowed field types for keyword search.
*
* @since 1.9.8.3
*
* @param array|mixed $fields Array of field types.
*
* @return array
*/
public function add_field_to_anti_spam_keyword_filter( $fields ): array {
$fields[] = $this->type;
return $fields;
}
/**
* Generate the HTML value for a field.
*
* It overrides the parent method because of fields with Other choices.
*
* @since 1.9.8.3
*
* @param mixed $value The value of the field.
* @param array $field Field data and settings.
* @param array $form_data Optional. Additional form data.
* @param string $context Optional. The context in which the value is being rendered.
*
* @return string
*/
public function field_html_value( $value, $field, $form_data = [], $context = '' ) {
if ( empty( $field['type'] ) || $field['type'] !== $this->type ) {
return $value;
}
$other_value = $this->get_other_choice_value( $field );
$field_value = $other_value ?? $value ?? '';
return parent::field_html_value(
$field_value,
$field,
$form_data,
$context
);
}
/**
* Return choice value, including Other label if applicable.
* It overrides the parent method because of fields with Other choices.
*
* @since 1.9.8.3
*
* @param array $field Field settings.
* @param array $form_data Form data.
*
* @return string
*/
protected function get_choices_value( array $field, array $form_data ): string {
$other_value = $this->get_other_choice_value( $field );
return $other_value ?? parent::get_choices_value( $field, $form_data );
}
/**
* Retrieve the value for the "Other" choice option in a field.
*
* This method handles the retrieval of the "Other" choice value, considering
* both raw and processed values. It supports cases where the "Other" choice
* is enabled and attempts to construct a meaningful representation of the value
* based on the available inputs.
*
* @since 1.9.8.3
*
* @param array $field Field data.
*
* @return string|null Returns the constructed "Other" choice value if available,
* or null if the "Other" choice is not enabled.
*/
private function get_other_choice_value( array $field ): ?string {
// Bail out early if it's not an Other choice.
if ( empty( $field['is_other'] ) ) {
return null;
}
$value = $field['value'] ?? '';
$value_raw = $field['value_raw'] ?? '';
if ( wpforms_is_empty_string( $value_raw ) ) {
return (string) $value;
}
// Return a value with a value_raw as a prefix only if both are not empty.
if ( ! wpforms_is_empty_string( $value ) ) {
return sprintf( '%1$s: %2$s', $value_raw, $value );
}
return (string) $value_raw;
}
/**
* Adjust the entry field before saving.
*
* It's necessary for fields with other choices.
*
* @since 1.9.8.3
*
* @param array $field Field data.
* @param array $form_data Form data.
* @param int $entry_id Entry ID.
*
* @return array
* @noinspection PhpUnused
*/
public function save_field( $field, $form_data, $entry_id ) {
if ( empty( $field['type'] ) || $field['type'] !== $this->type ) {
return $field;
}
// Save `value_raw: value` for fields with Other choices.
// It's necessary for search functionality on the Form Entries Overview table.
// Admins should be able to search for entries that have used an other value.
$other_value = $this->get_other_choice_value( $field );
if ( $other_value !== null ) {
$field['value'] = $other_value;
}
return $field;
}
}
new WPForms_Field_Radio();
@@ -0,0 +1,795 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// phpcs:disable Generic.Commenting.DocComment.MissingShort
/** @noinspection PhpIllegalPsrClassPathInspection */
/** @noinspection AutoloadingIssuesInspection */
// phpcs:enable Generic.Commenting.DocComment.MissingShort
/**
* Dropdown field.
*
* @since 1.0.0
*/
class WPForms_Field_Select extends WPForms_Field {
/**
* The 'Choices JS' version.
*
* @since 1.6.3
*/
public const CHOICES_VERSION = '10.2.0';
/**
* Classic (old) style.
*
* @since 1.6.1
*
* @var string
*/
public const STYLE_CLASSIC = 'classic';
/**
* Modern style.
*
* @since 1.6.1
*
* @var string
*/
public const STYLE_MODERN = 'modern';
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() { // phpcs:ignore WPForms.PHP.HooksMethod.InvalidPlaceForAddingHooks
// Define field type information.
$this->name = esc_html__( 'Dropdown', 'wpforms-lite' );
$this->keywords = esc_html__( 'choice', 'wpforms-lite' );
$this->type = 'select';
$this->icon = 'fa-caret-square-o-down';
$this->order = 70;
$this->defaults = [
1 => [
'label' => esc_html__( 'First Choice', 'wpforms-lite' ),
'value' => '',
'default' => '',
],
2 => [
'label' => esc_html__( 'Second Choice', 'wpforms-lite' ),
'value' => '',
'default' => '',
],
3 => [
'label' => esc_html__( 'Third Choice', 'wpforms-lite' ),
'value' => '',
'default' => '',
],
];
$this->default_settings = [
'choices' => $this->defaults,
];
// Define additional field properties.
add_filter( 'wpforms_field_properties_' . $this->type, [ $this, 'field_properties' ], 5, 3 );
// Form frontend CSS enqueues.
add_action( 'wpforms_frontend_css', [ $this, 'enqueue_frontend_css' ] );
// Form frontend JS enqueues.
add_action( 'wpforms_frontend_js', [ $this, 'enqueue_frontend_js' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_assets' ] );
}
/**
* Define additional field properties.
*
* @since 1.5.0
*
* @param array $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ) {
// Remove primary input.
unset( $properties['inputs']['primary'] );
// Define data.
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$choices = $field['choices'];
$dynamic = wpforms_get_field_dynamic_choices( $field, $form_id, $form_data );
if ( $dynamic !== false ) {
$choices = $dynamic;
$field['show_values'] = true;
}
// Set options container (<select>) properties.
$properties['input_container'] = [
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}",
'attr' => [
'name' => "wpforms[fields][{$field_id}]",
],
];
// Set properties.
foreach ( $choices as $key => $choice ) {
// Used for dynamic choices.
$depth = isset( $choice['depth'] ) ? absint( $choice['depth'] ) : 1;
$properties['inputs'][ $key ] = [
'container' => [
'attr' => [],
'class' => [ "choice-{$key}", "depth-{$depth}" ],
'data' => [],
'id' => '',
],
'label' => [
'attr' => [
'for' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
],
'class' => [ 'wpforms-field-label-inline' ],
'data' => [],
'id' => '',
'text' => $choice['label'],
],
'attr' => [
'name' => "wpforms[fields][{$field_id}]",
'value' => isset( $field['show_values'] ) ? $choice['value'] : $choice['label'],
],
'class' => [],
'data' => [],
'id' => "wpforms-{$form_id}-field_{$field_id}_{$key}",
'required' => ! empty( $field['required'] ) ? 'required' : '',
'default' => isset( $choice['default'] ),
];
}
// Add a class that changes the field size.
if ( ! empty( $field['size'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-' . esc_attr( $field['size'] );
}
// Required class for pagebreak validation.
if ( ! empty( $field['required'] ) ) {
$properties['input_container']['class'][] = 'wpforms-field-required';
}
// Add additional class for container.
if (
! empty( $field['style'] ) &&
in_array( $field['style'], [ self::STYLE_CLASSIC, self::STYLE_MODERN ], true )
) {
$properties['container']['class'][] = "wpforms-field-select-style-{$field['style']}";
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*
* @noinspection HtmlUnknownTarget*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Choices.
$this->field_option( 'choices', $field );
// AI Feature.
$this->field_option(
'ai_modal_button',
$field,
[
'value' => esc_html__( 'Generate Choices', 'wpforms-lite' ),
'type' => 'choices',
]
);
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Show Values toggle option. This option will only show if already used
// or if manually enabled by a filter.
if ( ! empty( $field['show_values'] ) || wpforms_show_fields_options_setting() ) {
$show_values = $this->field_element(
'toggle',
$field,
[
'slug' => 'show_values',
'value' => $field['show_values'] ?? '0',
'desc' => esc_html__( 'Show Values', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to manually set form field values.', 'wpforms-lite' ),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'show_values',
'content' => $show_values,
]
);
}
// Multiple options selection.
$fld = $this->field_element(
'toggle',
$field,
[
'slug' => 'multiple',
'value' => ! empty( $field['multiple'] ),
'desc' => esc_html__( 'Multiple Options Selection', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Allow users to select multiple choices in this field.', 'wpforms-lite' ) . '<br>' .
sprintf(
wp_kses( /* translators: %s - URL to WPForms.com doc article. */
esc_html__( 'For details, including how this looks and works for your site\'s visitors, please check out <a href="%s" target="_blank" rel="noopener noreferrer">our doc</a>.', 'wpforms-lite' ),
[
'a' => [
'href' => [],
'target' => [],
'rel' => [],
],
]
),
esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-allow-multiple-selections-to-a-dropdown-field-in-wpforms/', 'Field Options', 'Multiple Options Selection Documentation' ) )
),
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'multiple',
'content' => $fld,
]
);
// Style.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'style',
'value' => esc_html__( 'Style', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Classic style is the default one generated by your browser. Modern has a fresh look and displays all selected options in a single row.', 'wpforms-lite' ),
],
false
);
$fld = $this->field_element(
'select',
$field,
[
'slug' => 'style',
'value' => ! empty( $field['style'] ) ? $field['style'] : self::STYLE_CLASSIC,
'options' => [
self::STYLE_CLASSIC => esc_html__( 'Classic', 'wpforms-lite' ),
self::STYLE_MODERN => esc_html__( 'Modern', 'wpforms-lite' ),
],
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'style',
'content' => $lbl . $fld,
]
);
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Dynamic choice auto-populating toggle.
$this->field_option( 'dynamic_choices', $field );
// Dynamic choice source.
$this->field_option( 'dynamic_choices_source', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
* @since 1.6.1 Added a `Modern` style select support.
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
$args = [];
// Label.
$this->field_preview_option( 'label', $field );
// Prepare arguments.
$args['modern'] = false;
if (
! empty( $field['style'] ) &&
$field['style'] === self::STYLE_MODERN
) {
$args['modern'] = true;
$args['class'] = 'choicesjs-select';
}
// Choices.
$this->field_preview_option( 'choices', $field, $args );
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end and admin entry edit page.
*
* @since 1.0.0
* @since 1.5.0 Converted to a new format, where all the data are taken not from $deprecated, but field properties.
* @since 1.6.1 Added multiple select support.
*
* @param array $field Field data and settings.
* @param array $deprecated Deprecated array of field attributes.
* @param array $form_data Form data and settings.
*
* @noinspection HtmlUnknownAttribute
*/
public function field_display( $field, $deprecated, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$container = $field['properties']['input_container'];
$field_placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$is_multiple = ! empty( $field['multiple'] );
$is_modern = ! empty( $field['style'] ) && $field['style'] === self::STYLE_MODERN;
$choices = $field['properties']['inputs'];
// Do not display the field with empty choices on the frontend.
if ( ! $choices && ! is_admin() ) {
return;
}
// Display a warning message on the Entry Edit page.
if ( ! $choices && is_admin() ) {
$this->display_empty_dynamic_choices_message( $field );
return;
}
if ( ! empty( $field['properties']['input_container']['class'] ) && in_array( 'wpforms-field-required', $field['properties']['input_container']['class'], true ) ) {
$container['attr']['required'] = 'required';
}
// If it's multiple select.
if ( $is_multiple ) {
$container['attr']['multiple'] = 'multiple';
// Change a name attribute.
if ( ! empty( $container['attr']['name'] ) ) {
$container['attr']['name'] .= '[]';
}
}
// Add a class for Choices.js initialization.
if ( $is_modern ) {
$container['class'][] = 'choicesjs-select';
// Add a size-class to the data attribute - it is used when Choices.js is initialized.
if ( ! empty( $field['size'] ) ) {
$container['data']['size-class'] = 'wpforms-field-row wpforms-field-' . sanitize_html_class( $field['size'] );
}
$container['data']['search-enabled'] = $this->is_choicesjs_search_enabled( count( $choices ) );
}
$has_default = false;
// Check to see if any of the options were selected by default.
foreach ( $choices as $choice ) {
if ( ! empty( $choice['default'] ) ) {
$has_default = true;
break;
}
}
// Preselect default if no other choices were marked as default.
printf(
'<select %s>',
wpforms_html_attributes( $container['id'], $container['class'], $container['data'], $container['attr'] )
);
// Optional placeholder.
if ( ! empty( $field_placeholder ) || $is_modern ) {
printf(
'<option value="" class="placeholder" disabled %s>%s</option>',
selected( false, $has_default || $is_multiple, false ),
esc_html( $field_placeholder )
);
}
// Build the select options.
foreach ( $choices as $key => $choice ) {
$label = $this->get_choices_label( $choice['label']['text'] ?? '', $key, $field );
$value = isset( $choice['attr']['value'] ) && ! wpforms_is_empty_string( $choice['attr']['value'] ) ? $choice['attr']['value'] : $label;
$data = $choice['container']['data'] ?? [];
$data_html = '';
if ( ! empty( $data ) ) {
$data_html = wpforms_html_attributes( '', '', $data );
}
$selected = $choice['attr']['selected'] ?? false;
$selected_html = $selected ? ' selected="selected"' : '';
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
printf(
'<option value="%1$s" %2$s class="%3$s" %4$s %5$s>%6$s</option>',
esc_attr( $value ),
selected( true, ! empty( $choice['default'] ), false ),
esc_attr( implode( ' ', $choice['container']['class'] ) ),
$data_html,
$selected_html,
wp_kses(
$label,
[
'span' => [
'class' => [],
],
]
)
);
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</select>';
}
/**
* Validate field.
*
* @since 1.8.2
*
* @param int $field_id Field ID.
* @param string|array $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
// Skip validation if the field is dynamic and choices are empty.
if ( $this->is_dynamic_choices_empty( $field, $form_data ) ) {
return;
}
parent::validate( $field_id, $field_submit, $form_data );
}
/**
* Format and sanitize field.
*
* @since 1.0.2
* @since 1.6.1 Added support for multiple values.
*
* @param int $field_id Field ID.
* @param string|array $field_submit Submitted field value (selected option).
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.NestingLevel.MaxExceeded
$field = $form_data['fields'][ $field_id ];
$dynamic = ! empty( $field['dynamic_choices'] ) ? $field['dynamic_choices'] : false;
$multiple = ! empty( $field['multiple'] );
$name = sanitize_text_field( $field['label'] );
$value = [];
// Convert the submitted field value to array.
if ( ! is_array( $field_submit ) ) {
$field_submit = [ $field_submit ];
}
$value_raw = wpforms_sanitize_array_combine( $field_submit );
$data = [
'name' => $name,
'value' => '',
'value_raw' => $value_raw,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
if ( $dynamic === 'post_type' && ! empty( $field['dynamic_post_type'] ) ) {
// Dynamic population is enabled using post type (like for a `Checkboxes` field).
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'post_type';
$data['dynamic_items'] = $value_raw;
$data['dynamic_post_type'] = $field['dynamic_post_type'];
$posts = [];
foreach ( $field_submit as $id ) {
$post = get_post( $id );
if ( ! empty( $post ) && ! is_wp_error( $post ) && $data['dynamic_post_type'] === $post->post_type ) {
$posts[] = esc_html( wpforms_get_post_title( $post ) );
}
}
$data['value'] = ! empty( $posts ) ? wpforms_sanitize_array_combine( $posts ) : '';
} elseif ( $dynamic === 'taxonomy' && ! empty( $field['dynamic_taxonomy'] ) ) {
// Dynamic population is enabled using taxonomy (like for a `Checkboxes` field).
$value_raw = implode( ',', array_map( 'absint', $field_submit ) );
$data['value_raw'] = $value_raw;
$data['dynamic'] = 'taxonomy';
$data['dynamic_items'] = $value_raw;
$data['dynamic_taxonomy'] = $field['dynamic_taxonomy'];
$terms = [];
foreach ( $field_submit as $id ) {
$term = get_term( $id, $field['dynamic_taxonomy'] );
if ( ! empty( $term ) && ! is_wp_error( $term ) ) {
$terms[] = esc_html( wpforms_get_term_name( $term ) );
}
}
$data['value'] = ! empty( $terms ) ? wpforms_sanitize_array_combine( $terms ) : '';
} else {
// Normal processing, dynamic population is off.
// If show_values is true, that means values posted are the raw values
// and not the labels. So we need to get the label values.
if ( ! empty( $field['show_values'] ) && (int) $field['show_values'] === 1 ) {
foreach ( $field_submit as $item ) {
foreach ( $field['choices'] as $choice ) {
if ( $item === $choice['value'] ) {
$value[] = $choice['label'];
break;
}
}
}
$data['value'] = ! empty( $value ) ? wpforms_sanitize_array_combine( $value ) : '';
} else {
$data['value'] = $value_raw;
}
}
// Backward compatibility: for single dropdown save a string, for multiple - array.
if ( ! $multiple && is_array( $data ) && ( 1 === count( $data ) ) ) {
$data = reset( $data );
}
// Push field details to be saved.
wpforms()->obj( 'process' )->fields[ $field_id ] = $data;
}
/**
* Form frontend CSS enqueues.
*
* @since 1.6.1
*
* @param array $forms Forms on the current page.
*/
public function enqueue_frontend_css( $forms ) {
$has_modern_select = false;
foreach ( $forms as $form ) {
if ( $this->is_field_style( $form, self::STYLE_MODERN ) ) {
$has_modern_select = true;
break;
}
}
if ( $has_modern_select || wpforms()->obj( 'frontend' )->assets_global() ) {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-choicesjs',
WPFORMS_PLUGIN_URL . "assets/css/choices{$min}.css",
[],
self::CHOICES_VERSION
);
}
}
/**
* Form frontend JS enqueues.
*
* @since 1.6.1
*
* @param array $forms Forms on the current page.
*/
public function enqueue_frontend_js( $forms ) {
$has_modern_select = false;
foreach ( $forms as $form ) {
if ( $this->is_field_style( $form, self::STYLE_MODERN ) ) {
$has_modern_select = true;
break;
}
}
if ( $has_modern_select || wpforms()->obj( 'frontend' )->assets_global() ) {
$this->enqueue_choicesjs_once( $forms );
}
}
/**
* Load WPForms Gutenberg block scripts.
*
* @since 1.8.1
*/
public function enqueue_block_editor_assets() {
$min = wpforms_get_min_suffix();
wp_enqueue_style(
'wpforms-choicesjs',
WPFORMS_PLUGIN_URL . "assets/css/choices{$min}.css",
[],
self::CHOICES_VERSION
);
$this->enqueue_choicesjs_once( [] );
}
/**
* Whether the provided form has a dropdown field with a specified style.
*
* @since 1.6.1
*
* @param array $form Form data.
* @param string $style Desired field style.
*
* @return bool
*/
protected function is_field_style( $form, $style ) {
$is_field_style = false;
if ( empty( $form['fields'] ) ) {
return false;
}
foreach ( (array) $form['fields'] as $field ) {
if (
! empty( $field['type'] ) &&
$field['type'] === $this->type &&
! empty( $field['style'] ) &&
sanitize_key( $style ) === $field['style']
) {
$is_field_style = true;
break;
}
}
return $is_field_style;
}
/**
* Get a field name for an ajax error message.
*
* @since 1.6.3
*
* @param string|mixed $name Field name for error triggered.
* @param array $field Field settings.
* @param array $props List of properties.
* @param string|string[] $error Error message.
*
* @return string
* @noinspection PhpMissingReturnTypeInspection
* @noinspection ReturnTypeCanBeDeclaredInspection
*/
public function ajax_error_field_name( $name, $field, $props, $error ) {
$name = (string) $name;
if ( ! isset( $field['type'] ) || $field['type'] !== 'select' ) {
return $name;
}
if ( ! empty( $field['multiple'] ) ) {
$input = isset( $props['inputs'] ) ? end( $props['inputs'] ) : [];
return isset( $input['attr']['name'] ) ? $input['attr']['name'] . '[]' : '';
}
return $name;
}
}
new WPForms_Field_Select();
@@ -0,0 +1,558 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Single line text field.
*
* @since 1.0.0
*/
class WPForms_Field_Text extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Single Line Text', 'wpforms-lite' );
$this->type = 'text';
$this->icon = 'fa-text-width';
$this->order = 30;
// Define additional field properties.
add_filter( 'wpforms_field_properties_text', [ $this, 'field_properties' ], 5, 3 );
add_action( 'wpforms_frontend_js', [ $this, 'frontend_js' ] );
}
/**
* Convert mask formatted for jquery.inputmask into the format used by amp-inputmask.
*
* Note that amp-inputmask does not yet support all of the options that jquery.inputmask provides.
* In particular, amp-inputmask doesn't provides:
* - Upper-alphabetical mask.
* - Upper-alphanumeric mask.
* - Advanced Input Masks with arbitrary repeating groups.
*
* @link https://amp.dev/documentation/components/amp-inputmask
* @link https://wpforms.com/docs/how-to-use-custom-input-masks/
*
* @param string $mask Mask formatted for jquery.inputmask.
* @return array {
* Mask and placeholder.
*
* @type string $mask Mask for amp-inputmask.
* @type string $placeholder Placeholder derived from mask if one is not supplied.
* }
*/
protected function convert_mask_to_amp_inputmask( $mask ) {
$placeholder = '';
// Convert jquery.inputmask format into amp-inputmask format.
$amp_mask = '';
$req_mask_mapping = [
'9' => '0', // Numeric.
'a' => 'L', // Alphabetical (a-z or A-Z).
'A' => 'L', // Upper-alphabetical (A-Z). Note: AMP does not have an uppercase-alphabetical mask type, so same as previous.
'*' => 'A', // Alphanumeric (0-9, a-z, A-Z).
'&' => 'A', // Upper-alphanumeric (A-Z, 0-9). Note: AMP does not have an uppercase-alphanumeric mask type, so same as previous.
' ' => '_', // Automatically insert spaces.
];
$opt_mask_mapping = [
'9' => '9', // The user may optionally add a numeric character.
'a' => 'l', // The user may optionally add an alphabetical character.
'A' => 'l', // The user may optionally add an alphabetical character.
'*' => 'a', // The user may optionally add an alphanumeric character.
'&' => 'a', // The user may optionally add an alphanumeric character.
];
$placeholder_mapping = [
'9' => '0',
'a' => 'a',
'A' => 'a',
'*' => '_',
'&' => '_',
];
$is_inside_optional = false;
$last_mask_token = null;
for ( $i = 0, $len = strlen( $mask ); $i < $len; $i++ ) {
if ( '[' === $mask[ $i ] ) {
$is_inside_optional = true;
$placeholder .= $mask[ $i ];
continue;
} elseif ( ']' === $mask[ $i ] ) {
$is_inside_optional = false;
$placeholder .= $mask[ $i ];
continue;
} elseif ( isset( $last_mask_token ) && preg_match( '/^\{(?P<n>\d+)(?:,(?P<m>\d+))?\}/', substr( $mask, $i ), $matches ) ) {
$amp_mask .= str_repeat( $req_mask_mapping[ $last_mask_token ], $matches['n'] );
$placeholder .= str_repeat( $placeholder_mapping[ $last_mask_token ], $matches['n'] );
if ( isset( $matches['m'] ) ) {
$amp_mask .= str_repeat( $opt_mask_mapping[ $last_mask_token ], $matches['m'] );
$placeholder .= str_repeat( $placeholder_mapping[ $last_mask_token ], $matches['m'] );
}
$i += strlen( $matches[0] ) - 1;
$last_mask_token = null; // Reset.
continue;
}
if ( '\\' === $mask[ $i ] ) {
$amp_mask .= '\\';
$i++;
if ( ! isset( $mask[ $i ] ) ) {
continue;
}
$amp_mask .= $mask[ $i ];
} else {
// Remember this token in case it is a mask.
if ( isset( $opt_mask_mapping[ $mask[ $i ] ] ) ) {
$last_mask_token = $mask[ $i ];
}
if ( $is_inside_optional && isset( $opt_mask_mapping[ $mask[ $i ] ] ) ) {
$amp_mask .= $opt_mask_mapping[ $mask[ $i ] ];
} elseif ( isset( $req_mask_mapping[ $mask[ $i ] ] ) ) {
$amp_mask .= $req_mask_mapping[ $mask[ $i ] ];
} else {
$amp_mask .= '\\' . $mask[ $i ];
}
}
if ( isset( $placeholder_mapping[ $mask[ $i ] ] ) ) {
$placeholder .= $placeholder_mapping[ $mask[ $i ] ];
} else {
$placeholder .= $mask[ $i ];
}
}
return [ $amp_mask, $placeholder ];
}
/**
* Define additional field properties.
*
* @since 1.4.5
*
* @param array $properties Field properties.
* @param array $field Field settings.
* @param array $form_data Form data and settings.
*
* @return array
*/
public function field_properties( $properties, $field, $form_data ) {
// Input primary: Detect custom input mask.
if ( empty( $field['input_mask'] ) ) {
return $properties;
}
// Add class that will trigger custom mask.
$properties['inputs']['primary']['class'][] = 'wpforms-masked-input';
if ( wpforms_is_amp() ) {
return $this->get_amp_input_mask_properties( $properties, $field );
}
$properties['inputs']['primary']['data']['rule-inputmask-incomplete'] = true;
if ( strpos( $field['input_mask'], 'alias:' ) !== false ) {
$mask = str_replace( 'alias:', '', $field['input_mask'] );
$properties['inputs']['primary']['data']['inputmask-alias'] = $mask;
return $properties;
}
if ( strpos( $field['input_mask'], 'regex:' ) !== false ) {
$mask = str_replace( 'regex:', '', $field['input_mask'] );
$properties['inputs']['primary']['data']['inputmask-regex'] = $mask;
return $properties;
}
if ( strpos( $field['input_mask'], 'date:' ) !== false ) {
$mask = str_replace( 'date:', '', $field['input_mask'] );
$properties['inputs']['primary']['data']['inputmask-alias'] = 'datetime';
$properties['inputs']['primary']['data']['inputmask-inputformat'] = $mask;
/**
* Some datetime formats include letters, so we need to switch inputmode to text.
* For instance:
* tt is am/pm
* TT is AM/PM
*/
$properties['inputs']['primary']['data']['inputmask-inputmode'] = preg_match( '/[tT]/', $mask ) ? 'text' : 'numeric';
return $properties;
}
$properties['inputs']['primary']['data']['inputmask-mask'] = $field['input_mask'];
return $properties;
}
/**
* Define additional field properties for the inputmask on AMP pages.
*
* @since 1.7.6
*
* @param array $properties Field properties.
* @param array $field Field settings.
*
* @return array
*/
private function get_amp_input_mask_properties( $properties, $field ) {
list( $amp_mask, $placeholder ) = $this->convert_mask_to_amp_inputmask( $field['input_mask'] );
$properties['inputs']['primary']['attr']['mask'] = $amp_mask;
if ( empty( $properties['inputs']['primary']['attr']['placeholder'] ) ) {
$properties['inputs']['primary']['attr']['placeholder'] = $placeholder;
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'open',
]
);
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Limit length.
$args = [
'slug' => 'limit_enabled',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'limit_enabled',
'value' => isset( $field['limit_enabled'] ),
'desc' => esc_html__( 'Limit Length', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to limit text length by characters or words count.', 'wpforms-lite' ),
],
false
),
];
$this->field_element( 'row', $field, $args );
$count = $this->field_element(
'text',
$field,
[
'type' => 'number',
'slug' => 'limit_count',
'attrs' => [
'min' => 1,
'step' => 1,
'pattern' => '[0-9]',
],
'value' => ! empty( $field['limit_count'] ) ? absint( $field['limit_count'] ) : 1,
],
false
);
$mode = $this->field_element(
'select',
$field,
[
'slug' => 'limit_mode',
'value' => ! empty( $field['limit_mode'] ) ? esc_attr( $field['limit_mode'] ) : 'characters',
'options' => [
'characters' => esc_html__( 'Characters', 'wpforms-lite' ),
'words' => esc_html__( 'Words', 'wpforms-lite' ),
],
],
false
);
$args = [
'slug' => 'limit_controls',
'class' => ! isset( $field['limit_enabled'] ) ? 'wpforms-hide' : '',
'content' => $count . $mode,
];
$this->field_element( 'row', $field, $args );
// Default value.
$this->field_option( 'default_value', $field );
// Input Mask.
$lbl = $this->field_element(
'label',
$field,
[
'slug' => 'input_mask',
'value' => esc_html__( 'Input Mask', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Enter your custom input mask.', 'wpforms-lite' ),
'after_tooltip' => '<a href="' . esc_url( wpforms_utm_link( 'https://wpforms.com/docs/how-to-use-custom-input-masks/', 'Field Options', 'Input Mask Documentation' ) ) . '" class="after-label-description" target="_blank" rel="noopener noreferrer">' . esc_html__( 'See Examples & Docs', 'wpforms-lite' ) . '</a>',
],
false
);
$fld = $this->field_element(
'text',
$field,
[
'slug' => 'input_mask',
'value' => ! empty( $field['input_mask'] ) ? esc_attr( $field['input_mask'] ) : '',
],
false
);
$this->field_element(
'row',
$field,
[
'slug' => 'input_mask',
'content' => $lbl . $fld,
]
);
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field settings.
*/
public function field_preview( $field ) {
// Define data.
$placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
// Label.
$this->field_preview_option( 'label', $field );
// Primary input.
echo '<input type="text" placeholder="' . esc_attr( $placeholder ) . '" value="' . esc_attr( $default_value ) . '" class="primary-input" readonly>';
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field settings.
* @param array $deprecated Deprecated.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
if ( isset( $field['limit_enabled'] ) ) {
$limit_count = isset( $field['limit_count'] ) ? absint( $field['limit_count'] ) : 0;
$limit_mode = isset( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$primary['data']['form-id'] = $form_data['id'];
$primary['data']['field-id'] = $field['id'];
if ( 'characters' === $limit_mode ) {
$primary['class'][] = 'wpforms-limit-characters-enabled';
$primary['attr']['maxlength'] = $limit_count;
$primary['data']['text-limit'] = $limit_count;
} else {
$primary['class'][] = 'wpforms-limit-words-enabled';
$primary['data']['text-limit'] = $limit_count;
}
}
// Primary field.
printf(
'<input type="text" %s %s>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
$primary['required'] // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
/**
* Enqueue frontend limit option js.
*
* @since 1.5.6
*
* @param array $forms Forms on the current page.
*/
public function frontend_js( $forms ) {
// Get fields.
$fields = array_map(
function( $form ) {
return empty( $form['fields'] ) ? [] : $form['fields'];
},
(array) $forms
);
// Make fields flat.
$fields = array_reduce(
$fields,
function( $accumulator, $current ) {
return array_merge( $accumulator, $current );
},
[]
);
// Leave only fields with limit.
$fields = array_filter(
$fields,
function( $field ) {
return $field['type'] === $this->type && isset( $field['limit_enabled'] ) && ! empty( $field['limit_count'] );
}
);
if ( count( $fields ) ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-text-limit',
WPFORMS_PLUGIN_URL . "assets/js/frontend/fields/text-limit.es5{$min}.js",
[],
WPFORMS_VERSION,
$this->load_script_in_footer()
);
}
}
/**
* Format and sanitize field.
*
* @since 1.5.6
*
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
$name = ! empty( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';
// Sanitize.
$value = sanitize_text_field( $field_submit );
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => $name,
'value' => $value,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Validate field on form submit.
*
* @since 1.6.2
*
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
parent::validate( $field_id, $field_submit, $form_data );
if ( empty( $form_data['fields'][ $field_id ] ) || empty( $form_data['fields'][ $field_id ]['limit_enabled'] ) ) {
return;
}
$field = $form_data['fields'][ $field_id ];
$limit = absint( $field['limit_count'] );
$mode = ! empty( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$value = sanitize_text_field( $field_submit );
if ( 'characters' === $mode ) {
if ( mb_strlen( str_replace( "\r\n", "\n", $value ) ) > $limit ) {
/* translators: %s - limit characters number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d character.', 'Text can\'t exceed %d characters.', $limit, 'wpforms-lite' ), $limit );
return;
}
} else {
if ( wpforms_count_words( $value ) > $limit ) {
/* translators: %s - limit words number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d word.', 'Text can\'t exceed %d words.', $limit, 'wpforms-lite' ), $limit );
return;
}
}
}
}
new WPForms_Field_Text();
@@ -0,0 +1,380 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Paragraph text field.
*
* @since 1.0.0
*/
class WPForms_Field_Textarea extends WPForms_Field {
/**
* Primary class constructor.
*
* @since 1.0.0
*/
public function init() {
// Define field type information.
$this->name = esc_html__( 'Paragraph Text', 'wpforms-lite' );
$this->keywords = esc_html__( 'textarea', 'wpforms-lite' );
$this->type = 'textarea';
$this->icon = 'fa-paragraph';
$this->order = 50;
add_action( 'wpforms_frontend_js', [ $this, 'frontend_js' ] );
}
/**
* Get the value, that is used to prefill via dynamic or fallback population.
* Based on field data and current properties.
*
* @since 1.6.4
*
* @param string $raw_value Value from a GET param, always a string.
* @param string $input Represent a subfield inside the field. May be empty.
* @param array $properties Field properties.
* @param array $field Current field specific data.
*
* @return array Modified field properties.
*/
protected function get_field_populated_single_property_value( $raw_value, $input, $properties, $field ) {
if ( ! is_string( $raw_value ) ) {
return $properties;
}
if (
! empty( $input ) &&
isset( $properties['inputs'][ $input ] )
) {
$properties['inputs'][ $input ]['attr']['value'] = wpforms_sanitize_textarea_field( wp_unslash( $raw_value ) );
}
return $properties;
}
/**
* Field options panel inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data and settings.
*/
public function field_options( $field ) {
/*
* Basic field options.
*/
// Options open markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'open',
]
);
// Label.
$this->field_option( 'label', $field );
// Description.
$this->field_option( 'description', $field );
// Required toggle.
$this->field_option( 'required', $field );
// Options close markup.
$this->field_option(
'basic-options',
$field,
[
'markup' => 'close',
]
);
/*
* Advanced field options.
*/
// Options open markup.
$args = [
'markup' => 'open',
];
$this->field_option( 'advanced-options', $field, $args );
// Size.
$this->field_option( 'size', $field );
// Placeholder.
$this->field_option( 'placeholder', $field );
// Limit length.
$args = [
'slug' => 'limit_enabled',
'content' => $this->field_element(
'toggle',
$field,
[
'slug' => 'limit_enabled',
'value' => isset( $field['limit_enabled'] ) ? '1' : '0',
'desc' => esc_html__( 'Limit Length', 'wpforms-lite' ),
'tooltip' => esc_html__( 'Check this option to limit text length by characters or words count.', 'wpforms-lite' ),
],
false
),
];
$this->field_element( 'row', $field, $args );
$count = $this->field_element(
'text',
$field,
[
'type' => 'number',
'slug' => 'limit_count',
'attrs' => [
'min' => 1,
'step' => 1,
'pattern' => '[0-9]',
],
'value' => ! empty( $field['limit_count'] ) ? $field['limit_count'] : 1,
],
false
);
$mode = $this->field_element(
'select',
$field,
[
'slug' => 'limit_mode',
'value' => ! empty( $field['limit_mode'] ) ? esc_attr( $field['limit_mode'] ) : 'characters',
'options' => [
'characters' => esc_html__( 'Characters', 'wpforms-lite' ),
'words' => esc_html__( 'Words', 'wpforms-lite' ),
],
],
false
);
$args = [
'slug' => 'limit_controls',
'class' => ! isset( $field['limit_enabled'] ) ? 'wpforms-hide' : '',
'content' => $count . $mode,
];
$this->field_element( 'row', $field, $args );
// Default value.
$this->field_option( 'default_value', $field );
// Custom CSS classes.
$this->field_option( 'css', $field );
// Hide label.
$this->field_option( 'label_hide', $field );
// Options close markup.
$this->field_option(
'advanced-options',
$field,
[
'markup' => 'close',
]
);
}
/**
* Field preview inside the builder.
*
* @since 1.0.0
*
* @param array $field Field data and settings.
*/
public function field_preview( $field ) {
// Label.
$this->field_preview_option( 'label', $field );
// Primary input.
$placeholder = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';
$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';
echo '<textarea placeholder="' . esc_attr( $placeholder ) . '" class="primary-input" readonly>' . esc_textarea( $default_value ) . '</textarea>';
// Description.
$this->field_preview_option( 'description', $field );
}
/**
* Field display on the form front-end.
*
* @since 1.0.0
*
* @param array $field Field data and settings.
* @param array $deprecated Deprecated.
* @param array $form_data Form data and settings.
*/
public function field_display( $field, $deprecated, $form_data ) {
// Define data.
$primary = $field['properties']['inputs']['primary'];
$value = '';
if ( isset( $primary['attr']['value'] ) ) {
$value = esc_textarea( html_entity_decode( $primary['attr']['value'] ) );
unset( $primary['attr']['value'] );
}
if ( isset( $field['limit_enabled'] ) ) {
$limit_count = isset( $field['limit_count'] ) ? absint( $field['limit_count'] ) : 0;
$limit_mode = isset( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$primary['data']['form-id'] = $form_data['id'];
$primary['data']['field-id'] = $field['id'];
if ( 'characters' === $limit_mode ) {
$primary['class'][] = 'wpforms-limit-characters-enabled';
$primary['attr']['maxlength'] = $limit_count;
$primary['data']['text-limit'] = $limit_count;
} else {
$primary['class'][] = 'wpforms-limit-words-enabled';
$primary['data']['text-limit'] = $limit_count;
}
}
// Primary field.
printf(
'<textarea %s %s>%s</textarea>',
wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),
$primary['required'], // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$value // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
}
/**
* Enqueue frontend limit option js.
*
* @since 1.5.6
*
* @param array $forms Forms on the current page.
*/
public function frontend_js( $forms ) {
// Get fields.
$fields = array_map(
function( $form ) {
return empty( $form['fields'] ) ? [] : $form['fields'];
},
(array) $forms
);
// Make fields flat.
$fields = array_reduce(
$fields,
function( $accumulator, $current ) {
return array_merge( $accumulator, $current );
},
[]
);
// Leave only fields with limit.
$fields = array_filter(
$fields,
function( $field ) {
return $field['type'] === $this->type && isset( $field['limit_enabled'] );
}
);
if ( count( $fields ) ) {
$min = wpforms_get_min_suffix();
wp_enqueue_script(
'wpforms-text-limit',
WPFORMS_PLUGIN_URL . "assets/js/frontend/fields/text-limit.es5{$min}.js",
[],
WPFORMS_VERSION,
$this->load_script_in_footer()
);
}
}
/**
* Format and sanitize field.
*
* @since 1.5.6
*
* @param int $field_id Field ID.
* @param mixed $field_submit Field value that was submitted.
* @param array $form_data Form data and settings.
*/
public function format( $field_id, $field_submit, $form_data ) {
$field = $form_data['fields'][ $field_id ];
if ( is_array( $field_submit ) ) {
$field_submit = implode( "\r\n", array_filter( $field_submit ) );
}
$name = ! empty( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';
// Sanitize but keep line breaks.
$value = wpforms_sanitize_textarea_field( $field_submit );
wpforms()->obj( 'process' )->fields[ $field_id ] = [
'name' => $name,
'value' => $value,
'id' => wpforms_validate_field_id( $field_id ),
'type' => $this->type,
];
}
/**
* Validate field on form submit.
*
* @since 1.6.2
*
* @param int $field_id Field ID.
* @param mixed $field_submit Submitted field value (raw data).
* @param array $form_data Form data and settings.
*/
public function validate( $field_id, $field_submit, $form_data ) {
parent::validate( $field_id, $field_submit, $form_data );
if ( empty( $form_data['fields'][ $field_id ] ) || empty( $form_data['fields'][ $field_id ]['limit_enabled'] ) ) {
return;
}
if ( is_array( $field_submit ) ) {
$field_submit = implode( "\r\n", array_filter( $field_submit ) );
}
$field = $form_data['fields'][ $field_id ];
$limit = absint( $field['limit_count'] );
$mode = ! empty( $field['limit_mode'] ) ? sanitize_key( $field['limit_mode'] ) : 'characters';
$value = wpforms_sanitize_textarea_field( $field_submit );
if ( 'characters' === $mode ) {
if ( mb_strlen( str_replace( "\r\n", "\n", $value ) ) > $limit ) {
/* translators: %s - limit characters number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d character.', 'Text can\'t exceed %d characters.', $limit, 'wpforms-lite' ), $limit );
return;
}
} else {
if ( wpforms_count_words( $value ) > $limit ) {
/* translators: %s - limit words number. */
wpforms()->obj( 'process' )->errors[ $form_data['id'] ][ $field_id ] = sprintf( _n( 'Text can\'t exceed %d word.', 'Text can\'t exceed %d words.', $limit, 'wpforms-lite' ), $limit );
return;
}
}
}
}
new WPForms_Field_Textarea();