'required' => ! empty( $field['required'] ) ? 'required' : '',
'for' => sprintf( self::FIELD_FORMAT, $form_id, $field_id ),
'class' => [ 'wpforms-error' ],
'class' => $attributes['description_class'],
'id' => implode( '', array_slice( $attributes['description_id'], 0 ) ),
'value' => ! empty( $field['description'] ) ? wpforms_process_smart_tags( $field['description'], $form_data, [], '', 'field-properties' ) : '',
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
* Filters field properties.
* @param array $properties Field properties.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
$properties = (array) apply_filters( "wpforms_field_properties_{$field['type']}", $properties, $field, $form_data );
* @param array $properties Field properties.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
return (array) apply_filters( 'wpforms_field_properties', $properties, $field, $form_data );
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
* Prepare get_field_properties.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
* @param array $attributes List of field attributes.
private function prepare_get_field_properties( array $field, array $form_data, array $attributes ): array {
$attributes = empty( $attributes ) ? $this->get_field_attributes( $field, $form_data ) : $attributes;
$field = $this->filter_field( $field, $form_data, $attributes );
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
$error = ! empty( wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] ) ? wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] : '';
return [ $field, $attributes, $error ];
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
* @param array $attributes Field attributes.
private function filter_field( array $field, array $form_data, array $attributes ): array {
// This filter is for backwards compatibility purposes.
$types = [ 'text', 'textarea', 'name', 'number', 'email', 'hidden', 'url', 'html', 'divider', 'password', 'phone', 'address', 'select', 'checkbox', 'radio' ];
if ( in_array( $field['type'], $types, true ) ) {
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
* @param array $field Field data and settings.
* @param array $attributes Field attributes.
* @param array $form_data Form data and settings.
$filtered_field = apply_filters( "wpforms_{$field['type']}_field_display", $field, $attributes, $form_data );
$field = wpforms_list_intersect_key( (array) $filtered_field, $field );
} elseif ( $field['type'] === 'credit-card' ) {
* Filters credit card field.
* @param array $field Field data and settings.
* @param array $attributes Field attributes.
* @param array $form_data Form data and settings.
$filtered_field = apply_filters( 'wpforms_creditcard_field_display', $field, $attributes, $form_data );
$field = wpforms_list_intersect_key( (array) $filtered_field, $field );
} elseif ( in_array( $field['type'], [ 'payment-multiple', 'payment-single', 'payment-checkbox' ], true ) ) {
$filter_field_type = str_replace( '-', '_', $field['type'] );
* @param array $field Field data and settings.
* @param array $attributes Field attributes.
* @param array $form_data Form data and settings.
$filtered_field = apply_filters( 'wpforms_' . $filter_field_type . '_field_display', $field, $attributes, $form_data );
$field = wpforms_list_intersect_key( (array) $filtered_field, $field );
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function field_container_open( $field, $form_data ): void {
$this->render_obj->field_container_open( $field, $form_data );
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function field_container_close( $field, $form_data ): void {
$this->render_obj->field_container_close( $field, $form_data );
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function field_fieldset_open( $field, $form_data ): void {
$this->render_obj->field_fieldset_open( $field, $form_data );
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function field_fieldset_close( $field, $form_data ): void {
$this->render_obj->field_fieldset_close( $field, $form_data );
* Display the label for each field.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function field_label( $field, $form_data ): void {
$label = $field['properties']['label'];
// If the label is empty or disabled, don't proceed.
if ( empty( $label['value'] ) || $label['disabled'] ) {
$this->render_obj->field_label( $field, $form_data );
* Display any errors for each field.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function field_error( $field, $form_data ): void {
$error = $field['properties']['error'];
// If there are no errors, don't proceed.
// Advanced fields with multiple inputs (address, name, etc.) errors
// will be an array and are handled within the respective field class.
if ( empty( $error['value'] ) || is_array( $error['value'] ) ) {
$this->render_obj->field_error( $field, $form_data );
* Display the description for each field.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
* @noinspection HtmlUnknownAttribute
* @noinspection PhpUnusedParameterInspection
public function field_description( $field, $form_data ): void {
$action = current_action();
$description = $field['properties']['description'];
// If the description is empty, don't proceed.
if ( empty( $description['value'] ) ) {
// Determine positioning.
if ( $action === 'wpforms_display_field_before' && $description['position'] !== 'before' ) {
if ( $action === 'wpforms_display_field_after' && $description['position'] !== 'after' ) {
if ( $description['position'] === 'before' ) {
$description['class'][] = 'before';
$this->render_obj->field_description( $field, $form_data );
* Anti-spam honeypot output if configured.
* @param array $form_data Form data and settings.
* @param null $deprecated Deprecated in v1.3.7, previously was $form object.
* @param bool $title Whether to display form title.
* @param bool $description Whether to display form description.
* @param array $errors List of all errors filled in WPForms_Process::process().
* @noinspection PhpUnusedParameterInspection
public function honeypot( $form_data, $deprecated, $title, $description, $errors ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
empty( $form_data['settings']['honeypot'] ) ||
$form_data['settings']['honeypot'] !== '1'
$names = [ 'Name', 'Phone', 'Comment', 'Message', 'Email', 'Website' ];
echo '<div class="wpforms-field wpforms-field-hp">';
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<label for="wpforms-' . $form_data['id'] . '-field-hp" class="wpforms-field-label">' . $names[ array_rand( $names ) ] . '</label>';
echo '<input type="text" name="wpforms[hp]" id="wpforms-' . $form_data['id'] . '-field-hp" class="wpforms-field-medium">';
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
* @param array $form_data Form data and settings.
* @param null $deprecated Deprecated in v1.3.7, previously was $form object.
* @param bool $title Whether to display form title.
* @param bool $description Whether to display form description.
* @param array $errors List of all errors filled in WPForms_Process::process().
* @noinspection HtmlUnknownTarget
* @noinspection HtmlUnknownAttribute
* @noinspection PhpUnusedParameterInspection
public function foot( $form_data, $deprecated, $title, $description, $errors ): void {
// Do not render footer if there are no fields on the front.
if ( empty( $this->rendered_fields ) ) {
$form_id = absint( $form_data['id'] );
$settings = $form_data['settings'];
$submit_text = ! empty( $settings['submit_text'] ) ? $settings['submit_text'] : __( 'Submit', 'wpforms-lite' );
* Filter the form submit button text.
* @param string $submit_text Submit button text.
* @param array $form_data Form data.
$submit = apply_filters( 'wpforms_field_submit', $submit_text, $form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'aria-live' => 'assertive',
'value' => 'wpforms-submit',
* Filter the form submit button classes.
* @param array $classes Button classes.
* @param array $form_data Form data.
$classes = (array) apply_filters( 'wpforms_frontend_foot_submit_classes', [], $form_data );
// A lot of our frontend logic is dependent on this class, so we need to make sure it's present.
$classes = array_merge( $classes, [ 'wpforms-submit' ] );
] = $this->check_submit_settings( $settings, $form_id, $submit, $attrs, $data_attrs, $classes );
// AMP submit error template.
$this->amp_obj->output_error_template();
// Output footer errors if they exist.
if ( ! empty( $errors['footer'] ) ) {
$this->form_error( 'footer', $errors['footer'] );
$this->render_obj->submit_container_open( $this->pages, $form_data );
echo '<input type="hidden" name="wpforms[id]" value="' . absint( $form_id ) . '">';
if ( is_user_logged_in() ) {
value="<?php echo esc_attr( wp_create_nonce( "wpforms::form_{$form_id}" ) ); ?>"
echo '<input type="hidden" name="page_title" value="' . esc_attr( wpforms_process_smart_tags( '{page_title}', [], [], '', 'frontend-foot-hidden-input' ) ) . '">';
echo '<input type="hidden" name="page_url" value="' . esc_url( wpforms_process_smart_tags( '{page_url}', [], [], '', 'frontend-foot-hidden-input' ) ) . '">';
echo '<input type="hidden" name="url_referer" value="' . esc_url( wpforms_process_smart_tags( '{url_referer}', [], [], '', 'frontend-foot-hidden-input' ) ) . '">';
// The field is used for some smart tags determination.
echo '<input type="hidden" name="page_id" value="' . absint( get_the_ID() ) . '">';
// The field is used for setting global $post during AJAX submissions.
echo '<input type="hidden" name="wpforms[post_id]" value="' . absint( get_the_ID() ) . '">';
* Fires before 'submit' button.
* @param array $form_data Form data and settings.
do_action( 'wpforms_display_submit_before', $form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$this->render_obj->submit_button( $form_id, $submit, $classes, $data_attrs, $attrs, $form_data );
if ( ! empty( $settings['ajax_submit'] ) && ! $this->amp_obj->is_amp() ) {
* Filter submit spinner image src attribute.
* @see This filter is documented in wp-includes/plugin.php
$src = apply_filters_deprecated(
'wpforms_display_sumbit_spinner_src',
WPFORMS_PLUGIN_URL . 'assets/images/submit-spin.svg',
'wpforms_display_submit_spinner_src'
* Filter submit spinner image src attribute.
* @param string $src Spinner image source.
* @param array $form_data Form data and settings.
$src = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_display_submit_spinner_src',
$this->render_obj->submit_spinner( $src, $form_data );
* Runs right after form Submit button rendering.
* @since 1.7.5 Added new parameter for detecting button type.
* @param array $form_data Form data.
* @param string $button Button type, e.g. `submit`, `next`.
do_action( 'wpforms_display_submit_after', $form_data, 'submit' ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$this->render_obj->submit_container_close( $form_data );
// Load the success template in AMP.
$this->amp_obj->output_success_template( $form_data );
* Check submit settings and adjust attributes and classes.
* @param array $settings Settings.
* @param int $form_id Form id.
* @param string $submit Submit button text.
* @param array $attrs Attributes.
* @param array $data_attrs Data attributes.
* @param array $classes Classes.
private function check_submit_settings( $settings, $form_id, $submit, $attrs, $data_attrs, $classes ): array {
// Check for the 'submit' button alt-text.
if ( ! empty( $settings['submit_text_processing'] ) ) {
if ( $this->amp_obj->is_amp() ) {
$attrs['[text]'] = $this->amp_obj->get_text_attr( $form_id, $settings, $submit );
$data_attrs['alt-text'] = $settings['submit_text_processing'];
$data_attrs['submit-text'] = $submit;
// Check user defined submit button classes.
if ( ! empty( $settings['submit_class'] ) ) {
$submit_classes = is_array( $settings['submit_class'] ) ?
$settings['submit_class'] :
array_filter( explode( ' ', $settings['submit_class'] ) );
$classes = array_merge( $classes, $submit_classes );
return [ $attrs, $data_attrs, $classes ];
* @since 1.8.1 Added $form_data optional parameter.