: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* 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.).
public function assets_header() {
* Allow loading assets in 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 header on archive pages or any other
* pages that you know have a form - as a sidebar widget, dynamically inserted
* on form preview page, on category pages, etc.
* @param bool $force_load Force loading assets in header, default `false`.
$force_load = (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';
"wpforms-{$this->render_engine}-{$style_name}",
WPFORMS_PLUGIN_URL . "assets/css/frontend/{$this->render_engine}/wpforms-{$style_name}{$min}.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();
// Load 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",
* Retrieve the string containing the CAPTCHA inline javascript.
* The only reason we haven't removed this method at all is that it's protected.
* There is a non-zero probability that people have their own classes that extend Frontend class.
* @param array $captcha_settings The CAPTCHA settings.
* @noinspection PhpMissingParamTypeInspection
* @noinspection PhpMissingReturnTypeInspection
* @noinspection ReturnTypeCanBeDeclaredInspection
* @noinspection PhpUnusedParameterInspection
protected function get_captcha_inline_script( $captcha_settings ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
_deprecated_function( __METHOD__, '1.8.2 of the WPForms plugin', '\WPForms\Frontend\Captcha::get_captcha_inline_script' );
* 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 = [] ) {
$form_data = (array) $form_data;
$min = wpforms_get_min_suffix();
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 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 footer if needed (archives, widgets, etc.).
public function assets_footer() {
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 - characters count, %2$s - characters limit. */
esc_html__( '%1$s of %2$s max characters.', 'wpforms-lite' ),
'val_limit_words' => wpforms_setting(
sprintf( /* translators: %1$s - words count, %2$s - words limit. */
esc_html__( '%1$s of %2$s max words.', '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(),
'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 ] = 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' ) {
$css_vars_obj = wpforms()->get( 'css_vars' );
if ( empty( $css_vars_obj ) ) {
$strings['css_vars'] = array_keys( $css_vars_obj->get_vars( ':root' ) );
* Hook at fires at a later priority in wp_footer.
* @since 1.7.0 Load wpforms_settings on the confirmation page for a non-ajax form.
public function footer_end() {
( empty( $this->forms ) && empty( $_POST['wpforms'] ) && ! $this->assets_global() ) || // phpcs:ignore WordPress.Security.NonceVerification.Missing
$strings = $this->get_strings();
* Below we do our own implementation of wp_localize_script in an effort
* to be better compatible with caching plugins which were causing
echo "<script type='text/javascript'>\n";
echo "/* <![CDATA[ */\n";
echo 'var wpforms_settings = ' . wp_json_encode( $strings ) . "\n";
* Fires after the end of the footer.
* @param array $forms Forms being shown.
do_action( 'wpforms_wp_footer_end', $this->forms ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName