forms = [];
$this->hooks();
// Register shortcode.
add_shortcode( 'wpforms', [ $this, 'shortcode' ] );
}
/**
* Register hooks.
*
* @since 1.7.7
*/
private function hooks() {
// Filters.
add_filter( 'amp_skip_post', [ $this, 'amp_skip_post' ] );
add_filter( 'script_loader_tag', [ $this, 'set_defer_attribute' ], 10, 3 );
// Actions.
add_action( 'wpforms_frontend_output_success', [ $this, 'confirmation' ], 10, 3 );
add_action( 'wpforms_frontend_output', [ $this, 'head' ], 5, 5 );
add_action( 'wpforms_frontend_output', [ $this, 'fields' ], 10, 5 );
add_action( 'wpforms_display_field_before', [ $this, 'field_container_open' ], 5, 2 );
add_action( 'wpforms_display_field_before', [ $this, 'field_label' ], 15, 2 );
add_action( 'wpforms_display_field_before', [ $this, 'field_description' ], 20, 2 );
add_action( 'wpforms_display_field_after', [ $this, 'field_error' ], 3, 2 );
add_action( 'wpforms_display_field_after', [ $this, 'field_description' ], 5, 2 );
add_action( 'wpforms_display_field_after', [ $this, 'field_container_close' ], 15, 2 );
add_action( 'wpforms_frontend_output', [ $this, 'recaptcha' ], 20, 5 );
add_action( 'wpforms_frontend_output', [ $this, 'foot' ], 25, 5 );
add_action( 'wp_enqueue_scripts', [ $this, 'assets_header' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'recaptcha_noconflict' ], 9999 );
add_action( 'wp_footer', [ $this, 'assets_footer' ], 15 );
add_action( 'wp_footer', [ $this, 'recaptcha_noconflict' ], 19 );
add_action( 'wp_footer', [ $this, 'missing_assets_error_js' ], 20 );
add_action( 'wp_footer', [ $this, 'footer_end' ], 99 );
}
/**
* Get the amp-state ID for a given form.
*
* @since 1.5.4.2
*
* @param int $form_id Form ID.
*
* @return string State ID.
*/
protected function get_form_amp_state_id( $form_id ) {
return sprintf( 'wpforms_form_state_%d', $form_id );
}
/**
* Disable AMP if query param is detected.
*
* This allows the full form to be accessible for Pro users or sites
* that do not have SSL.
*
* @since 1.5.3
*
* @param bool $skip Skip AMP mode, display full post.
*
* @return bool
*/
public function amp_skip_post( $skip ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
return isset( $_GET['nonamp'] ) ? true : $skip;
}
/**
* Primary function to render a form on the frontend.
*
* @since 1.0.0
*
* @param int $id Form ID.
* @param bool $title Whether to display form title.
* @param bool $description Whether to display form description.
*/
public function output( $id, $title = false, $description = false ) {
if ( empty( $id ) ) {
return;
}
// Grab the form data, if not found then we bail.
$form = wpforms()->get( 'form' )->get( (int) $id );
if ( empty( $form ) ) {
return;
}
// We should display only the published form.
if ( ! empty( $form->post_status ) && $form->post_status !== 'publish' ) {
return;
}
// Basic information.
/**
* Filter frontend form data.
*
* @since 1.4.3
*
* @param array $form_data Form data.
*/
$form_data = apply_filters( 'wpforms_frontend_form_data', wpforms_decode( $form->post_content ) );
$form_id = absint( $form->ID );
$action = esc_url_raw( remove_query_arg( 'wpforms' ) );
$errors = empty( wpforms()->process->errors[ $form_id ] ) ? [] : wpforms()->process->errors[ $form_id ];
$title = filter_var( $title, FILTER_VALIDATE_BOOLEAN );
$description = filter_var( $description, FILTER_VALIDATE_BOOLEAN );
/**
* Is the form is empty?
* Check before output the form on the frontend.
*
* @since 1.7.7
*
* @param bool $form_is_empty Is the form is empty?
* @param array $form_data Form data.
*/
$form_is_empty = apply_filters( 'wpforms_frontend_output_form_is_empty', empty( $form_data['fields'] ), $form_data );
// If the form does not contain any fields - do not proceed.
if ( $form_is_empty ) {
echo '';
return;
}
// We need to stop output processing in case we are on AMP page.
if (
// phpcs:disable WPForms.PHP.ValidateHooks.InvalidHookName
wpforms_is_amp( false ) &&
(
! current_theme_supports( 'amp' ) ||
/**
* Filters the pro status of the plugin.
*
* @since 1.5.4.2
*
* @param bool $pro Pro status..
*/
apply_filters( 'wpforms_amp_pro', wpforms()->is_pro() ) ||
! is_ssl() ||
! defined( 'AMP__VERSION' ) ||
version_compare( AMP__VERSION, '1.2', '<' )
)
// phpcs:enable WPForms.PHP.ValidateHooks.InvalidHookName
) {
$full_page_url = home_url( add_query_arg( 'nonamp', '1' ) . '#wpforms-' . absint( $form->ID ) );
/**
* Allow modifying the text or url for the full page on the AMP pages.
*
* @since 1.4.1.1
* @since 1.7.1 Added $form_id, $full_page_url, and $form_data arguments.
*
* @param string $text Text.
* @param int $form_id Form id.
* @param string $full_page_url Full page url.
* @param array $form_data Form data and settings.
*
* @return string
*/
$text = (string) apply_filters(
'wpforms_frontend_shortcode_amp_text',
sprintf( /* translators: %s - URL to a non-amp version of a page with the form. */
__( 'Go to the full page to view and submit the form.', 'wpforms-lite' ),
esc_url( $full_page_url )
),
$form_id,
$full_page_url,
$form_data
);
printf(
'
%s
',
wp_kses_post( $text )
);
return;
}
// Add url query var wpforms_form_id to track post_max_size overflows.
if ( in_array( 'file-upload', wp_list_pluck( $form_data['fields'], 'type' ), true ) ) {
$action = add_query_arg( 'wpforms_form_id', $form_id, $action );
}
/**
* Fires before frontend output.
*
* @since 1.0.0
*
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
*/
do_action( 'wpforms_frontend_output_before', $form_data, $form );
// Check for return hash.
if (
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
! empty( $_GET['wpforms_return'] ) &&
wpforms()->process->valid_hash &&
absint( wpforms()->process->form_data['id'] ) === $form_id
) {
$this->form_container_open( $form_data, $form );
do_action( 'wpforms_frontend_output_success', wpforms()->process->form_data, wpforms()->process->fields, wpforms()->process->entry_id );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
wpforms_debug_data( $_POST );
$this->form_container_close( $form_data, $form );
return;
}
// Check for error-free completed form.
if (
empty( $errors ) &&
! empty( $form_data ) &&
! empty( $_POST['wpforms']['id'] ) &&
absint( $_POST['wpforms']['id'] ) === $form_id
) {
$is_ajax = wp_doing_ajax();
// There is no need for a container wrapper when a form is submitted through AJAX.
if ( ! $is_ajax ) {
$this->form_container_open( $form_data, $form );
}
do_action( 'wpforms_frontend_output_success', $form_data, false, false );
if ( ! $is_ajax ) {
$this->form_container_close( $form_data, $form );
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
wpforms_debug_data( $_POST );
return;
}
// Allow filter to return early if some condition is not met.
if ( ! apply_filters( 'wpforms_frontend_load', true, $form_data, null ) ) {
$this->form_container_open( $form_data, $form );
do_action( 'wpforms_frontend_not_loaded', $form_data, $form );
$this->form_container_close( $form_data, $form );
return;
}
// All checks have passed, so calculate multi-page details for the form.
$pages = wpforms_get_pagebreak_details( $form_data );
if ( $pages ) {
$this->pages = $pages;
} else {
$this->pages = false;
}
/**
* Allow modifying a form action attribute.
*
* @since 1.1.2
*
* @param string $action Action attribute.
* @param array $form_data Form data and settings.
* @param null $deprecated A deprecated argument.
*/
$action = apply_filters( 'wpforms_frontend_form_action', $action, $form_data, null );
$form_classes = [ 'wpforms-validate', 'wpforms-form' ];
if ( ! empty( $form_data['settings']['ajax_submit'] ) && ! wpforms_is_amp() ) {
$form_classes[] = 'wpforms-ajax-form';
}
$form_atts = [
'id' => sprintf( 'wpforms-form-%d', absint( $form_id ) ),
'class' => $form_classes,
'data' => [
'formid' => absint( $form_id ),
],
'atts' => [
'method' => 'post',
'enctype' => 'multipart/form-data',
'action' => esc_url( $action ),
],
];
if ( wpforms_is_amp() ) {
// Set submitting state.
if ( ! isset( $form_atts['atts']['on'] ) ) {
$form_atts['atts']['on'] = '';
} else {
$form_atts['atts']['on'] .= ';';
}
$form_atts['atts']['on'] .= sprintf(
'submit:AMP.setState( %1$s ); submit-success:AMP.setState( %2$s ); submit-error:AMP.setState( %2$s );',
wp_json_encode(
[
$this->get_form_amp_state_id( $form_id ) => [ 'submitting' => true ],
]
),
wp_json_encode(
[
$this->get_form_amp_state_id( $form_id ) => [ 'submitting' => false ],
]
)
);
// Upgrade the form to be an amp-form to avoid sanitizer conversion.
if ( isset( $form_atts['atts']['action'] ) ) {
$form_atts['atts']['action-xhr'] = $form_atts['atts']['action'];
unset( $form_atts['atts']['action'] );
$form_atts['atts']['verify-xhr'] = $form_atts['atts']['action-xhr'];
}
}
/**
* Allow modifying form attributes.
*
* @since 1.4.5
*
* @param array $form_atts Form attributes.
* @param array $form_data Form data and settings.
*/
$form_atts = apply_filters( 'wpforms_frontend_form_atts', $form_atts, $form_data );
$this->form_container_open( $form_data, $form );
do_action( 'wpforms_frontend_output_form_before', $form_data, $form );
echo '';
/**
* Allow adding content after a form.
*
* @since 1.5.4.2
*
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
*/
do_action( 'wpforms_frontend_output_form_after', $form_data, $form );
$this->form_container_close( $form_data, $form );
// Add form to class property that tracks all forms in a page.
$this->forms[ $form_id ] = $form_data;
// Optional debug information if WPFORMS_DEBUG is defined.
wpforms_debug_data( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
/**
* Fires after frontend output.
*
* @since 1.0.0
*
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
*/
do_action( 'wpforms_frontend_output_after', $form_data, $form );
}
/**
* Display form confirmation message.
*
* @since 1.0.0
*
* @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 ) {
$class = intval( wpforms_setting( 'disable-css', '1' ) ) === 1 ? 'wpforms-confirmation-container-full' : 'wpforms-confirmation-container';
// In AMP, just print template.
if ( wpforms_is_amp() ) {
$this->assets_confirmation( $form_data );
printf( '
{{{message}}}
', esc_attr( $class ) );
return;
}
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;
}
$confirmation = wpforms()->get( 'process' )->get_current_confirmation();
$confirmation_message = wpforms()->get( 'process' )->get_confirmation_message( $form_data, $fields, $entry_id );
// Only display if a confirmation message has been configured.
if ( empty( $confirmation ) || empty( $confirmation_message ) ) {
return;
}
// Load confirmation specific assets.
$this->assets_confirmation( $form_data );
/**
* Fires once before the confirmation message.
*
* @since 1.6.9
*
* @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 .= $this->confirmation_message_scroll ? ' wpforms-confirmation-scroll' : '';
printf(
'
%s
',
wpforms_sanitize_classes( $class ),
absint( $form_data['id'] ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$confirmation_message
);
/**
* Fires once after the confirmation message.
*
* @since 1.6.9
*
* @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 );
}
/**
* Form container classes.
*
* @since 1.7.9
*
* @param array $form_data Form data and settings.
*
* @return array
*/
private function get_container_classes( $form_data ) {
$classes = (int) wpforms_setting( 'disable-css', '1' ) === 1 ? [ 'wpforms-container-full' ] : [];
/**
* Allow form container classes to be filtered and user defined classes.
*
* @since 1.0.0
*
* @param array $classes Classes.
* @param array $form_data Form data and settings.
*/
$classes = 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'] ) );
}
return $classes;
}
/**
* Display the opening container markup for a form.
*
* @since 1.7.9
*
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
*/
private function form_container_open( $form_data, $form ) {
/**
* Fires before container open tag.
*
* @since 1.5.4.2
*
* @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 );
printf( '
', wpforms_sanitize_classes( $classes, true ), absint( $form->ID ) );
}
/**
* Display the closing container markup for a form.
*
* @since 1.7.9
*
* @param array $form_data Form data and settings.
* @param WP_Post $form Form post type.
*/
private function form_container_close( $form_data, $form ) {
echo '
';
/**
* Fires after container close tag.
*
* @since 1.5.4.2
*
* @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.
*
* @since 1.0.0
*
* @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().
*/
public function head( $form_data, $deprecated, $title, $description, $errors ) {
$settings = $form_data['settings'];
// Output title and/or description.
if ( $title === true || $description === true ) {
echo '