* @param string $type Error type.
* @param string $error Error text.
* @param array $form_data Form data. Defaults to null.
* Added to pass the form data in the case of
* the method is called inside the ajax callback.
public function form_error( string $type, $error, $form_data = null ): void {
if ( ! empty( $form_data ) ) {
$this->render_obj->form_data = $form_data;
$this->render_obj->form_error( $type, $error );
* Determine if we should load assets globally.
* If false, assets will load conditionally (default).
public function assets_global(): bool {
// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
* @param bool $are_assets_global Global assets.
return (bool) apply_filters( 'wpforms_global_assets', wpforms_setting( 'global-assets' ) ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Load the necessary assets for single pages/posts earlier if possible.
* If we are viewing a singular page, then we can check the content early
* to see if the shortcode was used. If not, we fall back and load the assets
* later on during the page (widgets, archives, etc.).
* @since 1.9.0 Added load JS assets.
public function assets_header(): void {
// Force loading JS assets in the header.
if ( ! $this->load_script_in_footer() ) {
* Allow loading assets in the header on various pages.
* By default, assets are loaded only on singular pages if WPForms shortcode or editor block is present.
* However, if a form is added as a sidebar widget, in a template or somewhere else outside the Loop,
* we will discover that too late for assets to be included in the header.
* In this case, we will include all required assets in the footer instead.
* This may lead to a brief FOUC (Flash Of Unstyled Content).
* Returning `true` from this filter on a particular page that matches your criteria is useful
* if you need to load assets in the header on archive pages or any other pages that you know have a form.
* It may be as a sidebar widget, dynamically inserted on form preview page, on category pages, etc.
* @param bool $force_load Force loading assets in the header, default `false`.
$force_load_css = (bool) apply_filters( 'wpforms_frontend_assets_header_force_load', false );
has_shortcode( $post->post_content, 'wpforms' ) ||
( function_exists( 'has_block' ) && has_block( 'wpforms/form-selector' ) )
* Load the CSS assets for frontend output.
public function assets_css() {
* Fires before enqueueing frontend CSS.
* @param array $forms Array of forms on the page.
do_action( 'wpforms_frontend_css', $this->forms ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
$min = wpforms_get_min_suffix();
$disable_css = (int) wpforms_setting( 'disable-css', '1' );
if ( $disable_css === 3 ) {
$style_name = $disable_css === 1 ? 'full' : 'base';
$handle = "wpforms-{$this->render_engine}-{$style_name}";
WPFORMS_PLUGIN_URL . "assets/css/frontend/{$this->render_engine}/wpforms-{$style_name}{$min}.css",
// Add CSS variables for the Modern Markup mode for full styles.
if ( empty( $this->css_vars_obj ) || $this->render_engine !== 'modern' || $style_name !== 'full' ) {
wp_add_inline_style( $handle, $this->css_vars_obj->get_root_vars_css() );
* Load the JS assets for frontend output.
public function assets_js() {
if ( $this->amp_obj->is_amp() ) {
* Fire before frontend JS assets are loaded.
* @param array $forms Forms on the current page.
do_action( 'wpforms_frontend_js', $this->forms );
$min = wpforms_get_min_suffix();
$in_footer = $this->load_script_in_footer();
// Load the jQuery validation library - https://jqueryvalidation.org/.
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.validate.min.js',
// Load jQuery input mask library - https://github.com/RobinHerbots/jquery.inputmask.
$this->assets_global() ||
wpforms_has_field_type( [ 'phone', 'address' ], $this->forms, true ) ||
wpforms_has_field_setting( 'input_mask', $this->forms, true )
WPFORMS_PLUGIN_URL . 'assets/lib/jquery.inputmask.min.js',
// Load mailcheck <https://github.com/mailcheck/mailcheck> and punycode libraries.
$this->assets_global() ||
wpforms_has_field_type( [ 'email' ], $this->forms, true )
WPFORMS_PLUGIN_URL . 'assets/lib/mailcheck.min.js',
WPFORMS_PLUGIN_URL . 'assets/lib/punycode.min.js',
WPFORMS_PLUGIN_URL . "assets/js/share/utils{$min}.js",
WPFORMS_PLUGIN_URL . "assets/js/frontend/wpforms{$min}.js",
// Load JS additions needed in the Modern Markup mode.
if ( $this->render_engine === 'modern' ) {
WPFORMS_PLUGIN_URL . "assets/js/frontend/wpforms-modern{$min}.js",
* Cloudflare Turnstile captcha requires defer attribute.
* @param string $tag HTML for the script tag.
* @param string $handle Handle of a script.
* @param string $src Src of a script.
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpUnusedParameterInspection
public function set_defer_attribute( $tag, $handle, $src ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
$captcha_settings = wpforms_get_captcha_settings();
if ( $captcha_settings['provider'] !== 'turnstile' ) {
if ( $handle !== 'wpforms-recaptcha' ) {
return str_replace( ' src', ' defer src', $tag );
* Load the necessary assets for the confirmation message.
* @since 1.7.9 Added $form_data argument.
* @param array $form_data Form data and settings.
public function assets_confirmation( $form_data = [] ): void {
$form_data = (array) $form_data;
$min = wpforms_get_min_suffix();
$in_footer = $this->load_script_in_footer();
if ( (int) wpforms_setting( 'disable-css', '1' ) === 1 ) {
WPFORMS_PLUGIN_URL . "assets/css/frontend/{$this->render_engine}/wpforms-full{$min}.css",
// Special confirmation JS.
if ( ! $this->amp_obj->is_amp() ) {
WPFORMS_PLUGIN_URL . "assets/js/frontend/wpforms-confirmation{$min}.js",
* Fires after enqueueing assets on the confirmation page have been enqueued.
* @since 1.7.9 Added $form_data argument.
* @param array $form_data Form data and settings.
do_action( 'wpforms_frontend_confirmation', $form_data );
* Load the assets in the footer if needed (archives, widgets, etc.).
public function assets_footer(): void {
if ( empty( $this->forms ) && ! $this->assets_global() ) {
* Fires after enqueueing footer assets.
* @param array $forms Forms being shown.
do_action( 'wpforms_wp_footer', $this->forms ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Get strings to localize.
* @return array Array of strings to localize.
public function get_strings(): array {
'val_required' => wpforms_setting( 'validation-required', esc_html__( 'This field is required.', 'wpforms-lite' ) ),
'val_email' => wpforms_setting( 'validation-email', esc_html__( 'Please enter a valid email address.', 'wpforms-lite' ) ),
'val_email_suggestion' => wpforms_setting(
'validation-email-suggestion',
sprintf( /* translators: %s - suggested email address. */
esc_html__( 'Did you mean %s?', 'wpforms-lite' ),
'val_email_suggestion_title' => esc_attr__( 'Click to accept this suggestion.', 'wpforms-lite' ),
'val_email_restricted' => wpforms_setting( 'validation-email-restricted', esc_html__( 'This email address is not allowed.', 'wpforms-lite' ) ),
'val_number' => wpforms_setting( 'validation-number', esc_html__( 'Please enter a valid number.', 'wpforms-lite' ) ),
'val_number_positive' => wpforms_setting( 'validation-number-positive', esc_html__( 'Please enter a valid positive number.', 'wpforms-lite' ) ),
'val_minimum_price' => wpforms_setting( 'validation-minimum-price', esc_html__( 'Amount entered is less than the required minimum.', 'wpforms-lite' ) ),
'val_confirm' => wpforms_setting( 'validation-confirm', esc_html__( 'Field values do not match.', 'wpforms-lite' ) ),
'val_checklimit' => wpforms_setting( 'validation-check-limit', esc_html__( 'You have exceeded the number of allowed selections: {#}.', 'wpforms-lite' ) ),
'val_limit_characters' => wpforms_setting(
'validation-character-limit',
sprintf( /* translators: %1$s - character count, %2$s - character limit. */
esc_html__( '%1$s of %2$s max characters.', 'wpforms-lite' ),
'val_limit_words' => wpforms_setting(
sprintf( /* translators: %1$s - word count, %2$s - word limit. */
esc_html__( '%1$s of %2$s max words.', 'wpforms-lite' ),
'val_min' => wpforms_setting( 'validation-min', esc_html__( 'Please enter a value greater than or equal to {0}.', 'wpforms-lite' ) ),
'val_max' => wpforms_setting( 'validation-max', esc_html__( 'Please enter a value less than or equal to {0}.', 'wpforms-lite' ) ),
'val_recaptcha_fail_msg' => wpforms_setting( 'recaptcha-fail-msg', esc_html__( 'Google reCAPTCHA verification failed, please try again later.', 'wpforms-lite' ) ),
'val_turnstile_fail_msg' => wpforms_setting( 'turnstile-fail-msg', esc_html__( 'Cloudflare Turnstile verification failed, please try again later.', 'wpforms-lite' ) ),
'val_inputmask_incomplete' => wpforms_setting( 'validation-inputmask-incomplete', esc_html__( 'Please fill out the field in required format.', 'wpforms-lite' ) ),
'locale' => wpforms_get_language_code(),
* Filters the user's country code.
* Leave empty for most cases, it will be auto-detected.
* If set, it will make country recognition in wpforms.js frontend skipped.
* Allows testing Phone Smart field with different countries.
* @param string|false $country Country code.
'country' => apply_filters( 'wpforms_frontend_get_user_country_code', false ),
'country_list_label' => esc_html__( 'Country list', 'wpforms-lite' ),
'wpforms_plugin_url' => WPFORMS_PLUGIN_URL,
'gdpr' => wpforms_setting( 'gdpr' ),
'ajaxurl' => admin_url( 'admin-ajax.php' ),
* Filters mail check enabled flag.
* @param bool $flag Enabled flag.
'mailcheck_enabled' => (bool) apply_filters( 'wpforms_mailcheck_enabled', true ), // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Filters mail check domains.
* @param array $domains Domains to check.
'mailcheck_domains' => array_map( 'sanitize_text_field', (array) apply_filters( 'wpforms_mailcheck_domains', [] ) ), // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Filters toplevel domains for mail check.
* @param array $toplevel_domains Toplevel domains to check.
'mailcheck_toplevel_domains' => array_map( 'sanitize_text_field', (array) apply_filters( 'wpforms_mailcheck_toplevel_domains', [ 'dev' ] ) ), // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
// Include payment-related strings if needed.
$strings = $this->get_payment_strings( $strings );
// Include CSS variables list.
$strings = $this->get_css_vars_strings( $strings );
* Filters frontend strings.
* @param array $strings Frontend strings.
$strings = (array) apply_filters( 'wpforms_frontend_strings', $strings );
foreach ( $strings as $key => $value ) {
if ( ! is_scalar( $value ) ) {
$strings[ $key ] = esc_html( html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' ) );
* @param array $strings Strings.
private function get_payment_strings( array $strings ): array {
if ( function_exists( 'wpforms_get_currencies' ) ) {
$currency = wpforms_get_currency();
$currencies = wpforms_get_currencies();
$strings['currency_code'] = $currency;
$strings['currency_thousands'] = $currencies[ $currency ]['thousands_separator'] ?? ',';
$strings['currency_decimals'] = wpforms_get_currency_decimals( $currencies[ $currency ] );
$strings['currency_decimal'] = $currencies[ $currency ]['decimal_separator'] ?? '.';
$strings['currency_symbol'] = $currencies[ $currency ]['symbol'] ?? '$';
$strings['currency_symbol_pos'] = $currencies[ $currency ]['symbol_pos'] ?? 'left';
$strings['val_requiredpayment'] = wpforms_setting( 'validation-requiredpayment', esc_html__( 'Payment is required.', 'wpforms-lite' ) );
$strings['val_creditcard'] = wpforms_setting( 'validation-creditcard', esc_html__( 'Please enter a valid credit card number.', 'wpforms-lite' ) );
* Get CSS variables data.
* @param array $strings Strings.
private function get_css_vars_strings( array $strings ): array {
if ( wpforms_get_render_engine() !== 'modern' ) {
if ( empty( $this->css_vars_obj ) ) {