PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/frontend-assets.php +127 -20 2.10.1 → 2.12.8 View file →
@@ -8,8 +8,9 @@
8 8 */
9 9
10 10 namespace SRFM\Inc;
11 11
12 +use SRFM\Inc\Compatibility\Multilingual\String_Translator;
12 13 use SRFM\Inc\Traits\Get_Instance;
13 14 use SRFM\Inc\Payments\Payment_Helper;
14 15 use SRFM\Inc\Payments\Stripe\Stripe_Helper;
15 16
@@ -104,12 +105,27 @@
104 105
105 106 // Scripts.
106 107 foreach ( self::$js_assets as $handle => $name ) {
107 108 if ( 'form-submit' === $handle ) {
109 + // No 'wp-api-fetch' dependency: the script talks to the REST API via a
110 + // plain fetch() against the URLs localized below, not wp.apiFetch(),
111 + // so submissions no longer depend on that second script having loaded
112 + // and executed correctly. See the wp_localize_script() call below for
113 + // why wp.apiFetch's middleware (root-URL resolution, nonce injection)
114 + // isn't needed for either endpoint this script calls.
115 + //
116 + // 'wp-i18n' and 'wp-hooks' ARE required and must stay. The bundle imports
117 + // __() and applyFilters(), which @wordpress/scripts externalises to the
118 + // wp.i18n / window.wp.hooks globals instead of inlining — the generated
119 + // assets/build/formSubmit.asset.php is the authority on this list. They
120 + // used to arrive for free because 'wp-api-fetch' pulled them in through
121 + // its own dependency graph; dropping that above removed them, and an
122 + // undeclared wp.hooks is undefined under a JS-combining optimizer, which
123 + // kills every submission with the same TypeError this change prevents.
108 124 wp_register_script(
109 125 SRFM_SLUG . '-' . $handle,
110 126 SRFM_URL . 'assets/build/' . $name . '.js',
111 - [ 'wp-api-fetch' ],
127 + [ 'wp-i18n', 'wp-hooks' ],
112 128 SRFM_VER,
113 129 true
114 130 );
115 131 } else {
@@ -122,23 +138,55 @@
122 138 );
123 139 }
124 140 }
125 141
142 + $validation_messages = array_merge(
143 + Translatable::get_frontend_validation_messages(),
144 + [
145 + 'srfm_turnstile_error_message' => __( 'Turnstile sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
146 + 'srfm_google_captcha_error_message' => __( 'Google Captcha sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
147 + 'srfm_captcha_h_error_message' => __( 'HCaptcha sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
148 + ]
149 + );
150 +
151 + // Translate each validation message through the active provider so
152 + // admin-supplied translations from WPML String Translation win over the
153 + // .mo-file fallback. When no provider is active, this is a pass-through.
154 + $validation_messages = String_Translator::get_instance()->translate_validation_messages( $validation_messages );
155 +
126 156 wp_localize_script(
127 157 SRFM_SLUG . '-form-submit',
128 158 SRFM_SLUG . '_submit',
129 159 [
130 - 'site_url' => site_url(),
131 - 'nonce' => wp_create_nonce( 'wp_rest' ),
132 - 'messages' => array_merge(
133 - Translatable::get_frontend_validation_messages(),
134 - [
135 - 'srfm_turnstile_error_message' => __( 'Turnstile sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
136 - 'srfm_google_captcha_error_message' => __( 'Google Captcha sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
137 - 'srfm_captcha_h_error_message' => __( 'HCaptcha sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
138 - ]
139 - ),
140 - 'is_rtl' => $is_rtl,
160 + 'site_url' => site_url(),
161 + 'nonce' => wp_create_nonce( 'wp_rest' ),
162 + // Fully resolved REST endpoint URL, so the frontend can call it with a
163 + // plain fetch() instead of wp.apiFetch(). rest_url() already accounts
164 + // for pretty vs. plain permalinks (the latter needs a `?rest_route=`
165 + // query var rather than a path segment), subdirectory installs, and
166 + // multisite domain mapping — the same resolution wp.apiFetch's root-URL
167 + // middleware would otherwise do from a second, independently-loaded
168 + // script. submit-form's auth does not depend on that script either: it
169 + // is guarded by the X-WP-Submit-Token header (Submit_Token::verify()).
170 + //
171 + // The after-submission URL is deliberately NOT localized. It needs the
172 + // submission id and a per-submission nonce, so it is built server-side
173 + // and returned in the submit response instead (see Form_Submit). A base
174 + // URL here invited the client to concatenate those on, which silently
175 + // produced an unroutable URL wherever rest_url() returns a
176 + // `?rest_route=` form.
177 + 'submit_form_url' => esc_url_raw( rest_url( 'sureforms/v1/submit-form' ) ),
178 + 'messages' => $validation_messages,
179 + 'is_rtl' => $is_rtl,
180 + // Resolved RFC 5321 email limits so the client honors the
181 + // srfm_email_field_char_limits filter instead of hardcoding 64/255.
182 + 'email_char_limits' => Field_Validation::get_email_char_limits(),
183 + // Hint only. This value is baked into cached HTML and can be a full
184 + // cache TTL out of date, so the server re-checks on every write --
185 + // see Form_Submit::client_error_log_permissions_check(). Its job is
186 + // to keep the browser from posting when logging is plainly off.
187 + 'logging_enabled' => Client_Logger::is_enabled(),
188 + 'log_error_url' => esc_url_raw( rest_url( 'sureforms/v1/log-client-error' ) ),
141 189 ]
142 190 );
143 191
144 192 $current_post = get_post();
@@ -149,9 +197,10 @@
149 197 $load_assets = ( SRFM_FORMS_POST_TYPE === $current_post->post_type || ( false !== strpos( $current_post->post_content, 'wp:srfm/form' ) || has_shortcode( $current_post->post_content, 'sureforms' ) ) );
150 198
151 199 if ( $load_assets ) {
152 200 // Load needed styles in head tag if current requested page has SureForms form.
153 - self::enqueue_scripts_and_styles();
201 + // Skip the SureForms stylesheets when every form on the page has default styling disabled.
202 + self::enqueue_scripts_and_styles( Form_Styling::should_skip_frontend_styles( $current_post ) );
154 203 }
155 204 }
156 205 }
157 206
@@ -157,21 +206,27 @@
157 206
158 207 /**
159 208 * Enqueue scripts and styles.
160 209 *
210 + * @param bool $skip_form_styles When true, the SureForms stylesheets are not enqueued
211 + * (default styling disabled for the form). External library
212 + * styles and scripts are always loaded so advanced fields
213 + * like Dropdown and Phone keep working.
161 214 * @return void
162 215 * @since 0.0.11
163 216 */
164 - public static function enqueue_scripts_and_styles() {
217 + public static function enqueue_scripts_and_styles( $skip_form_styles = false ) {
165 218 // Load the styles.
166 - foreach ( self::$css_assets as $handle => $path ) {
219 + if ( ! $skip_form_styles ) {
220 + foreach ( self::$css_assets as $handle => $path ) {
167 221
168 - // Skip single form styles if not on single form page.
169 - if ( 'single' === $handle && ! is_singular( SRFM_FORMS_POST_TYPE ) ) {
170 - continue;
222 + // Skip single form styles if not on single form page.
223 + if ( 'single' === $handle && ! is_singular( SRFM_FORMS_POST_TYPE ) ) {
224 + continue;
225 + }
226 +
227 + wp_enqueue_style( SRFM_SLUG . '-' . $handle );
171 228 }
172 -
173 - wp_enqueue_style( SRFM_SLUG . '-' . $handle );
174 229 }
175 230
176 231 // Load the external styles. Like Phone and Tom Select.
177 232 foreach ( self::$css_external_assets as $handle => $path ) {
@@ -257,8 +312,20 @@
257 312 // Phone.js depends on intl-tel-input library (and i18n if loaded).
258 313 $block_dependencies = [ SRFM_SLUG . "-{$block_name}-intl-input-deps" ];
259 314 }
260 315 wp_enqueue_script( SRFM_SLUG . "-{$block_name}", $js_uri . $block_name . $file_prefix . '.js', $block_dependencies, SRFM_VER, true );
316 +
317 + if ( 'phone' === $block_name ) {
318 + // Geo-country endpoint for per-visitor auto country detection.
319 + // Built with rest_url() so it is correct regardless of permalink structure.
320 + wp_localize_script(
321 + SRFM_SLUG . "-{$block_name}",
322 + 'srfm_phone_data',
323 + [
324 + 'geo_endpoint' => esc_url_raw( rest_url( 'sureforms/v1/geo-country' ) ),
325 + ]
326 + );
327 + }
261 328 }
262 329
263 330 if ( 'input' === $block_name && isset( $attr['inputMask'] ) && 'none' !== $attr['inputMask'] ) {
264 331 // Input mask JS - only load when inputMask is configured.
@@ -480,8 +547,10 @@
480 547 // Bail if not SureForms post type.
481 548 return $template;
482 549 }
483 550
551 + self::reset_printed_assets();
552 +
484 553 $file_name = 'single-form.php';
485 554 $template = locate_template( $file_name );
486 555
487 556 /**
@@ -489,7 +558,45 @@
489 558 *
490 559 * @since 0.0.1
491 560 */
492 561 return apply_filters( 'srfm_form_template', $template ? $template : SRFM_DIR . '/templates/' . $file_name );
562 + }
563 +
564 + /**
565 + * Let the Instant Form template print assets a discarded render already claimed.
566 + *
567 + * `page_template()` runs on `template_include` at PHP_INT_MAX and returns the
568 + * Instant Form template regardless of what earlier filters returned. A page
569 + * builder that renders the whole page inside its own `template_include`
570 + * filter has therefore already run `wp_head()` and `wp_footer()` into an
571 + * output buffer that is about to be thrown away.
572 + *
573 + * The buffer goes, but `WP_Styles::$done` and `WP_Scripts::$done` still hold
574 + * every handle it claimed, so `do_items()` skips them when `single-form.php`
575 + * calls `wp_head()` and `wp_footer()` for real. The form arrives with no
576 + * stylesheets, and -- because `srfm-form-submit` is registered for the footer
577 + * -- no submit handler either.
578 + *
579 + * Clearing both `done` lists wholesale is the right scope rather than an
580 + * over-broad one: only the render that begins after this filter returns
581 + * reaches the browser, so nothing recorded before it was ever delivered. The
582 + * handles are re-enqueued by the second `wp_enqueue_scripts` pass, so they
583 + * print normally once `done` stops shadowing them.
584 + *
585 + * Guarded on `wp_head` having already fired, so this is inert on the ordinary
586 + * path where no builder rendered first and nothing has been printed yet.
587 + *
588 + * @since 2.12.7
589 + * @return void
590 + */
591 + private static function reset_printed_assets() {
592 + // No earlier wp_head() means no discarded render, so there is nothing to
593 + // forget. Deny is the fallthrough: act only on the state this repairs.
594 + if ( ! did_action( 'wp_head' ) ) {
595 + return;
596 + }
597 +
598 + wp_styles()->done = [];
599 + wp_scripts()->done = [];
493 600 }
494 601
495 602 }