* Check whether the output was successful.
* @param WP_Post $form Form.
private function output_success( WP_Post $form ): bool {
$form_id = absint( $form->ID );
$process = wpforms()->obj( 'process' );
$form_data = $process->form_data;
$errors = empty( $process->errors[ $form_id ] ) ? [] : $process->errors[ $form_id ];
// Check for return hash.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
! empty( $_GET['wpforms_return'] ) &&
(int) $form_data['id'] === $form_id
$this->form_container_open( $form_data, $form );
* Fires at successful output.
* @param array $form_data Form data.
* @param array $fields Form fields.
* @param int $entry_id Form ID.
do_action( 'wpforms_frontend_output_success', $form_data, $process->fields, $process->entry_id );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
wpforms_debug_data( $_POST );
$this->form_container_close( $form_data, $form );
// Check for the error-free completed form.
// phpcs:disable WordPress.Security.NonceVerification.Missing
! empty( $_POST['wpforms']['id'] ) &&
(int) $_POST['wpforms']['id'] === $form_id
// phpcs:enable WordPress.Security.NonceVerification.Missing
// There is no need for a container wrapper when a form is submitted through AJAX.
$this->form_container_open( $form_data, $form );
/** This action is documented in the same method, several lines above. */
do_action( 'wpforms_frontend_output_success', $form_data, $process->fields, $process->entry_id );
$this->form_container_close( $form_data, $form );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
wpforms_debug_data( $_POST );
* Display a form confirmation message.
* @param array $form_data Form data and settings.
* @param array $fields Sanitized field data.
* @param int $entry_id Entry id.
public function confirmation( $form_data, $fields = [], $entry_id = 0 ): void {
$form_data = (array) $form_data;
// In AMP, just print template.
if ( $this->amp_obj->output_success_template( $form_data ) ) {
[ $fields, $entry_id ] = $this->prepare_confirmation_args( $fields, $entry_id );
$process = wpforms()->obj( 'process' );
$confirmation = $process->get_current_confirmation();
$confirmation_message = $process->get_confirmation_message( $form_data, $fields, $entry_id );
// Only display if a confirmation message has been configured.
if ( empty( $confirmation ) || empty( $confirmation_message ) ) {
// Load confirmation-specific assets.
$this->assets_confirmation( $form_data );
* Fires once before the confirmation message.
* @param array $confirmation Current confirmation data.
* @param array $form_data Form data and settings.
* @param array $fields Sanitized field data.
* @param int $entry_id Entry id.
do_action( 'wpforms_frontend_confirmation_message_before', $confirmation, $form_data, $fields, $entry_id );
$class = (int) wpforms_setting( 'disable-css', '1' ) === 1 ?
'wpforms-confirmation-container-full' :
'wpforms-confirmation-container';
$class .= $this->confirmation_message_scroll ? ' wpforms-confirmation-scroll' : '';
$this->render_obj->confirmation( $confirmation_message, $class, $form_data );
* Fires once after the confirmation message.
* @param array $confirmation Current confirmation data.
* @param array $form_data Form data and settings.
* @param array $fields Sanitized field data.
* @param int $entry_id Entry id.
do_action( 'wpforms_frontend_confirmation_message_after', $confirmation, $form_data, $fields, $entry_id );
* Prepare confirmation arguments.
* @param array $fields Sanitized field data.
* @param int $entry_id Entry id.
private function prepare_confirmation_args( $fields = [], $entry_id = 0 ): array {
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
if ( empty( $fields ) ) {
$fields = ! empty( $_POST['wpforms']['complete'] ) ? $_POST['wpforms']['complete'] : [];
if ( empty( $entry_id ) ) {
$entry_id = ! empty( $_POST['wpforms']['entry_id'] ) ? $_POST['wpforms']['entry_id'] : 0;
// phpcs:enable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
return [ $fields, $entry_id ];
* Form container classes.
* @param array $form_data Form data and settings.
private function get_container_classes( $form_data ): array {
$classes = (int) wpforms_setting( 'disable-css', '1' ) === 1 ? [ 'wpforms-container-full' ] : [];
* Allow form container classes to be filtered and user-defined classes.
* @param array $classes Classes.
* @param array $form_data Form data and settings.
$classes = (array) apply_filters( 'wpforms_frontend_container_class', $classes, $form_data );
if ( ! empty( $form_data['settings']['form_class'] ) ) {
$classes = array_merge( $classes, explode( ' ', $form_data['settings']['form_class'] ) );
* Display the opening container markup for a form.
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
private function form_container_open( $form_data, $form ): void {
* Fires before container open tag.
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
do_action( 'wpforms_frontend_output_container_before', $form_data, $form );
$classes = $this->get_container_classes( $form_data );
$this->render_obj->form_container_open( $classes, $form_data );
* Display the closing container markup for a form.
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
private function form_container_close( $form_data, $form ): void {
$this->render_obj->form_container_close();
* Fires after container close tag.
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
do_action( 'wpforms_frontend_output_container_after', $form_data, $form );
* Form head area, for displaying form title and description if enabled.
* @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 head( array $form_data, $deprecated, bool $title, bool $description, $errors ): void {
// Output title and/or description.
if ( $title === true || $description === true ) {
$this->render_obj->form_head_container( $title, $description, $form_data );
* Filters <noscript> error message.
* @param string $message Message.
* @param array $form_data Form data.
$noscript_msg = apply_filters(
'wpforms_frontend_noscript_error_message',
__( 'Please enable JavaScript in your browser to complete this form.', 'wpforms-lite' ),
if ( ! empty( $noscript_msg ) && ! empty( $form_data['fields'] ) && ! $this->amp_obj->is_amp() ) {
$this->render_obj->noscript( $noscript_msg );
// Output header errors if they exist.
if ( ! empty( $errors['header'] ) ) {
$this->form_error( 'header', $errors['header'] );
* @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 fields( array $form_data, $deprecated, bool $title, bool $description, $errors ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
// We need to have form fields to proceed.
if ( empty( $form_data['fields'] ) ) {
* Filters the base level fields on the frontend.
* @param array $fields_data Form fields data.
$fields = (array) apply_filters( 'wpforms_frontend_fields_base_level', $form_data['fields'] );
$this->render_obj->fields_area_open();
* Core actions on this hook:
* 20 Pagebreak markup (open first page).
* @param array $form_data Form data.
do_action( 'wpforms_display_fields_before', $form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
// Loop through all the fields we have.
foreach ( $fields as $field ) {
$this->render_field( $form_data, $field );
* Core actions on this hook:
* 5 Pagebreak markup (close last page).
* @param array $form_data Form data.
do_action( 'wpforms_display_fields_after', $form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$this->render_obj->fields_area_close();
* Return base attributes for a specific field. This is deprecated and
* exists for backwards-compatibility purposes. Use field properties instead.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
public function get_field_attributes( array $field, array $form_data ): array {
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
'field_class' => [ 'wpforms-field', 'wpforms-field-' . sanitize_html_class( $field['type'] ) ],
'field_id' => [ sprintf( 'wpforms-%d-field_%s-container', $form_id, $field_id ) ],
'label_class' => [ 'wpforms-field-label' ],
'description_class' => [ 'wpforms-field-description' ],
'input_id' => [ sprintf( self::FIELD_FORMAT, $form_id, $field_id ) ],
// Check user field defined classes.
if ( ! empty( $field['css'] ) ) {
$attributes['field_class'] = array_merge( $attributes['field_class'], wpforms_sanitize_classes( $field['css'], true ) );
// Check for input column layouts.
$attributes = $this->check_input_columns( $field, $attributes );
// Check label visibility.
if ( ! empty( $field['label_hide'] ) ) {
$attributes['label_class'][] = 'wpforms-label-hide';
if ( ! empty( $field['size'] ) ) {
$attributes['input_class'][] = 'wpforms-field-' . sanitize_html_class( $field['size'] );
if ( ! empty( $field['required'] ) ) {
$attributes['input_class'][] = 'wpforms-field-required';
// Check if there are errors.
if ( ! empty( wpforms()->obj( 'process' )->errors[ $form_id ][ $field_id ] ) ) {
$attributes['input_class'][] = 'wpforms-error';
* Filters field attributes.
* This filter is deprecated, filter the properties (below) instead.
* @param array $attributes Field attributes.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
return (array) apply_filters( 'wpforms_field_atts', $attributes, $field, $form_data ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Check input column layouts and set relevant attributes.
* @param array $field Field data and settings.
* @param array $attributes Attributes.
private function check_input_columns( array $field, array $attributes ): array {
if ( ! empty( $field['input_columns'] ) ) {
if ( $field['input_columns'] === '2' ) {
$attributes['field_class'][] = 'wpforms-list-2-columns';
} elseif ( $field['input_columns'] === '3' ) {
$attributes['field_class'][] = 'wpforms-list-3-columns';
} elseif ( $field['input_columns'] === 'inline' ) {
$attributes['field_class'][] = 'wpforms-list-inline';
* Return base properties for a specific field.
* @param array $field Field data and settings.
* @param array $form_data Form data and settings.
* @param array $attributes List of field attributes.
public function get_field_properties( array $field, array $form_data, array $attributes = [] ): array {
[ $field, $attributes, $error ] = $this->prepare_get_field_properties( $field, $form_data, $attributes );
$form_id = absint( $form_data['id'] );
$field_id = wpforms_validate_field_id( $field['id'] );
'style' => $attributes['field_style'],
'class' => $attributes['field_class'],
'id' => implode( '', array_slice( $attributes['field_id'], 0 ) ),
'for' => sprintf( self::FIELD_FORMAT, $form_id, $field_id ),
'class' => $attributes['label_class'],
'disabled' => ! empty( $field['label_disable'] ),
'hidden' => ! empty( $field['label_hide'] ),
'id' => $attributes['label_id'],
'required' => ! empty( $field['required'] ),
'value' => ! empty( $field['label'] ) ? $field['label'] : '',
'name' => "wpforms[fields][{$field_id}]",
'value' => isset( $field['default_value'] ) ? wpforms_process_smart_tags( $field['default_value'], $form_data, [], '', 'field-properties' ) : '',
'placeholder' => $field['placeholder'] ?? '',
'class' => $attributes['input_class'],
'data' => $attributes['input_data'],
'id' => implode( array_slice( $attributes['input_id'], 0 ) ),