PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.3
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.3
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 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / frontend-assets.php
sureforms / inc Last commit date
abilities 3 weeks ago admin 3 months ago ai-form-builder 1 month ago blocks 2 months ago compatibility 3 weeks ago database 3 weeks ago email 3 weeks ago fields 3 weeks ago global-settings 1 month ago lib 1 month ago migrator 2 months ago page-builders 3 weeks ago payments 3 weeks ago single-form-settings 2 months ago traits 2 months ago activator.php 1 year ago admin-ajax.php 2 months ago background-process.php 9 months ago create-new-form.php 3 months ago duplicate-form.php 3 months ago entries.php 3 weeks ago events-scheduler.php 2 years ago export.php 3 months ago field-validation.php 3 weeks ago form-restriction.php 2 months ago form-styling.php 1 month ago form-submit.php 3 weeks ago forms-data.php 5 months ago frontend-assets.php 1 month ago generate-form-markup.php 3 weeks ago gutenberg-hooks.php 3 weeks ago helper.php 3 weeks ago learn.php 4 months ago onboarding.php 2 months ago post-types.php 1 month ago rest-api.php 3 weeks ago smart-tags.php 4 months ago submit-token.php 4 months ago translatable.php 1 month ago updater-callbacks.php 3 weeks ago updater.php 3 weeks ago
frontend-assets.php
526 lines
1 <?php
2 /**
3 * SureForms Public Class.
4 *
5 * Class file for public functions.
6 *
7 * @package SureForms
8 */
9
10 namespace SRFM\Inc;
11
12 use SRFM\Inc\Compatibility\Multilingual\String_Translator;
13 use SRFM\Inc\Traits\Get_Instance;
14 use SRFM\Inc\Payments\Payment_Helper;
15 use SRFM\Inc\Payments\Stripe\Stripe_Helper;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Public Class
23 *
24 * @since 0.0.1
25 */
26 class Frontend_Assets {
27 use Get_Instance;
28
29 /**
30 * JS Assets.
31 *
32 * @since 0.0.11
33 * @var array<string>
34 */
35 public static $js_assets = [
36 'form-submit' => 'formSubmit',
37 'frontend' => 'frontend',
38 ];
39
40 /**
41 * CSS Assets.
42 *
43 * @since 0.0.11
44 * @var array<string>
45 */
46 public static $css_assets = [
47 'frontend-default' => 'blocks/default/frontend',
48 'common' => 'common',
49 'form' => 'frontend/form',
50 'single' => 'single',
51 ];
52
53 /**
54 * External CSS Assets.
55 *
56 * @since 0.0.11
57 * @var array<string>
58 */
59 public static $css_external_assets = [
60 'tom-select' => 'tom-select',
61 'intl-tel-input' => 'intl/intlTelInput.min',
62 ];
63
64 /**
65 * Constructor
66 *
67 * @since 0.0.1
68 */
69 public function __construct() {
70 add_filter( 'template_include', [ $this, 'page_template' ], PHP_INT_MAX );
71 add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] );
72 add_filter( 'render_block', [ $this, 'generate_render_script' ], 10, 2 );
73 }
74
75 /**
76 * Enqueue Script.
77 *
78 * @return void
79 * @since 0.0.1
80 */
81 public function register_scripts() {
82 $file_prefix = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? '' : '.min';
83 $dir_name = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? 'unminified' : 'minified';
84 $js_uri = SRFM_URL . 'assets/js/' . $dir_name . '/';
85 $css_uri = SRFM_URL . 'assets/css/' . $dir_name . '/';
86 $css_vendor = SRFM_URL . 'assets/css/minified/deps/';
87 $is_rtl = is_rtl();
88 $rtl = $is_rtl ? '-rtl' : '';
89
90 $security_setting_options = get_option( 'srfm_security_settings_options' );
91 $is_set_v2_site_key = false;
92 if ( is_array( $security_setting_options ) && isset( $security_setting_options['srfm_v2_invisible_site_key'] ) && ! empty( $security_setting_options['srfm_v2_invisible_site_key'] ) ) {
93 $is_set_v2_site_key = true;
94 }
95
96 // Styles based on meta style.
97 foreach ( self::$css_assets as $handle => $path ) {
98 wp_register_style( SRFM_SLUG . '-' . $handle, $css_uri . $path . $file_prefix . $rtl . '.css', [], SRFM_VER );
99 }
100
101 // External styles.
102 foreach ( self::$css_external_assets as $handle => $path ) {
103 wp_register_style( SRFM_SLUG . '-' . $handle, $css_vendor . $path . '.css', [], SRFM_VER );
104 }
105
106 // Scripts.
107 foreach ( self::$js_assets as $handle => $name ) {
108 if ( 'form-submit' === $handle ) {
109 wp_register_script(
110 SRFM_SLUG . '-' . $handle,
111 SRFM_URL . 'assets/build/' . $name . '.js',
112 [ 'wp-api-fetch' ],
113 SRFM_VER,
114 true
115 );
116 } else {
117 wp_register_script(
118 SRFM_SLUG . '-' . $handle,
119 $js_uri . $name . $file_prefix . '.js',
120 [],
121 SRFM_VER,
122 true
123 );
124 }
125 }
126
127 $validation_messages = array_merge(
128 Translatable::get_frontend_validation_messages(),
129 [
130 'srfm_turnstile_error_message' => __( 'Turnstile sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
131 'srfm_google_captcha_error_message' => __( 'Google Captcha sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
132 'srfm_captcha_h_error_message' => __( 'HCaptcha sitekey verification failed. Please contact your site administrator.', 'sureforms' ),
133 ]
134 );
135
136 // Translate each validation message through the active provider so
137 // admin-supplied translations from WPML String Translation win over the
138 // .mo-file fallback. When no provider is active, this is a pass-through.
139 $validation_messages = String_Translator::get_instance()->translate_validation_messages( $validation_messages );
140
141 wp_localize_script(
142 SRFM_SLUG . '-form-submit',
143 SRFM_SLUG . '_submit',
144 [
145 'site_url' => site_url(),
146 'nonce' => wp_create_nonce( 'wp_rest' ),
147 'messages' => $validation_messages,
148 'is_rtl' => $is_rtl,
149 // Resolved RFC 5321 email limits so the client honors the
150 // srfm_email_field_char_limits filter instead of hardcoding 64/255.
151 'email_char_limits' => Field_Validation::get_email_char_limits(),
152 ]
153 );
154
155 $current_post = get_post();
156
157 // Let's conditionally load form assets if current requested page has our forms.
158 if ( $current_post instanceof \WP_Post ) {
159 // Handles condition for Instant Form, Block Embedded, and Shortcode Embedded forms.
160 $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' ) ) );
161
162 if ( $load_assets ) {
163 // Load needed styles in head tag if current requested page has SureForms form.
164 // Skip the SureForms stylesheets when every form on the page has default styling disabled.
165 self::enqueue_scripts_and_styles( Form_Styling::should_skip_frontend_styles( $current_post ) );
166 }
167 }
168 }
169
170 /**
171 * Enqueue scripts and styles.
172 *
173 * @param bool $skip_form_styles When true, the SureForms stylesheets are not enqueued
174 * (default styling disabled for the form). External library
175 * styles and scripts are always loaded so advanced fields
176 * like Dropdown and Phone keep working.
177 * @return void
178 * @since 0.0.11
179 */
180 public static function enqueue_scripts_and_styles( $skip_form_styles = false ) {
181 // Load the styles.
182 if ( ! $skip_form_styles ) {
183 foreach ( self::$css_assets as $handle => $path ) {
184
185 // Skip single form styles if not on single form page.
186 if ( 'single' === $handle && ! is_singular( SRFM_FORMS_POST_TYPE ) ) {
187 continue;
188 }
189
190 wp_enqueue_style( SRFM_SLUG . '-' . $handle );
191 }
192 }
193
194 // Load the external styles. Like Phone and Tom Select.
195 foreach ( self::$css_external_assets as $handle => $path ) {
196 wp_enqueue_style( SRFM_SLUG . '-' . $handle );
197 }
198
199 // Load the scripts.
200 foreach ( self::$js_assets as $handle => $path ) {
201 wp_enqueue_script( SRFM_SLUG . '-' . $handle );
202 }
203 }
204
205 /**
206 * Enqueue block scripts
207 *
208 * @param string $block_type block name.
209 * @param array<string, mixed> $attr Array of block attributes.
210 * @since 0.0.1
211 * @return void
212 */
213 public function enqueue_srfm_script( $block_type, $attr ) {
214 $block_name = str_replace( 'srfm/', '', $block_type );
215 // associative array to keep the count of block that requires scripts to work.
216 $script_dep_blocks = [
217 'dropdown' => 0,
218 'multi-choice' => 0,
219 'number' => 0,
220 'textarea' => 0,
221 'url' => 0,
222 'phone' => 0,
223 'input' => 0,
224 ];
225
226 $file_prefix = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? '' : '.min';
227 $dir_name = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? 'unminified' : 'minified';
228
229 // Check if block is in the array and check if block is already enqueued.
230 if (
231 in_array( $block_name, array_keys( $script_dep_blocks ), true ) &&
232 0 === $script_dep_blocks[ $block_name ]
233 ) {
234 $script_dep_blocks[ $block_name ] += 1;
235 $js_uri = SRFM_URL . 'assets/js/' . $dir_name . '/blocks/';
236 $js_vendor_uri = SRFM_URL . 'assets/js/minified/deps/';
237 $css_vendor_uri = SRFM_URL . 'assets/css/minified/deps/';
238 if ( 'phone' === $block_name ) {
239 // Enqueue main intl-tel-input library.
240 wp_enqueue_script( SRFM_SLUG . "-{$block_name}-intl-input-deps", $js_vendor_uri . 'intl/intTelInputWithUtils.min.js', [], SRFM_VER, true );
241
242 // Enqueue i18n translations if available for current locale.
243 self::enqueue_intl_tel_input_i18n(
244 SRFM_SLUG . "-{$block_name}-intl-i18n",
245 SRFM_SLUG . "-{$block_name}-intl-input-deps"
246 );
247 }
248
249 if ( 'dropdown' === $block_name ) {
250 // if the dropdown / address-compact block is after any other block, then we need to dequeue the srfm-form-submit script and enqueue it again and load it with tom-select dependency.
251 wp_dequeue_script( SRFM_SLUG . '-form-submit' );
252 wp_enqueue_script( SRFM_SLUG . '-dropdown', $js_uri . 'dropdown' . $file_prefix . '.js', [ 'wp-a11y' ], SRFM_VER, true );
253 wp_enqueue_script( SRFM_SLUG . '-tom-select', $js_vendor_uri . 'tom-select.min.js', [], SRFM_VER, true );
254 // frontend utils using dropdown dependency.
255 wp_enqueue_script(
256 SRFM_SLUG . '-form-submit',
257 SRFM_URL . 'assets/build/formSubmit.js',
258 [
259 'srfm-tom-select',
260 'srfm-dropdown',
261 'wp-api-fetch',
262 ],
263 SRFM_VER,
264 true
265 );
266 }
267
268 $is_not_dropdown = 'dropdown' !== $block_name;
269 $is_not_textarea = 'textarea' !== $block_name;
270
271 if ( $is_not_dropdown && $is_not_textarea ) {
272 // Set dependencies for phone block to ensure intl-tel-input loads first.
273 $block_dependencies = [];
274 if ( 'phone' === $block_name ) {
275 // Phone.js depends on intl-tel-input library (and i18n if loaded).
276 $block_dependencies = [ SRFM_SLUG . "-{$block_name}-intl-input-deps" ];
277 }
278 wp_enqueue_script( SRFM_SLUG . "-{$block_name}", $js_uri . $block_name . $file_prefix . '.js', $block_dependencies, SRFM_VER, true );
279
280 if ( 'phone' === $block_name ) {
281 // Geo-country endpoint for per-visitor auto country detection.
282 // Built with rest_url() so it is correct regardless of permalink structure.
283 wp_localize_script(
284 SRFM_SLUG . "-{$block_name}",
285 'srfm_phone_data',
286 [
287 'geo_endpoint' => esc_url_raw( rest_url( 'sureforms/v1/geo-country' ) ),
288 ]
289 );
290 }
291 }
292
293 if ( 'input' === $block_name && isset( $attr['inputMask'] ) && 'none' !== $attr['inputMask'] ) {
294 // Input mask JS - only load when inputMask is configured.
295 wp_enqueue_script( SRFM_SLUG . '-inputmask', $js_vendor_uri . 'inputmask.min.js', [], SRFM_VER, true );
296 }
297
298 // Adding js for the input textarea block.
299 if ( 'textarea' === $block_name && ! empty( $attr['isRichText'] ) ) {
300 wp_enqueue_script( SRFM_SLUG . '-quill-editor', $js_vendor_uri . '/quill.min.js', [], SRFM_VER, true );
301
302 wp_enqueue_style( SRFM_SLUG . '-quill-editor', $css_vendor_uri . 'quill/quill.snow.css', [], SRFM_VER );
303
304 wp_enqueue_script( SRFM_SLUG . '-textarea', $js_uri . 'textarea' . $file_prefix . '.js', [], SRFM_VER, true );
305
306 wp_localize_script(
307 SRFM_SLUG . '-textarea',
308 'srfm_quill_i18n',
309 [
310 'normal' => _x( 'Normal', 'Quill heading picker: default paragraph style', 'sureforms' ),
311 'heading_1' => __( 'Heading 1', 'sureforms' ),
312 'heading_2' => __( 'Heading 2', 'sureforms' ),
313 'heading_3' => __( 'Heading 3', 'sureforms' ),
314 'heading_4' => __( 'Heading 4', 'sureforms' ),
315 'heading_5' => __( 'Heading 5', 'sureforms' ),
316 'heading_6' => __( 'Heading 6', 'sureforms' ),
317 'visit_url' => _x( 'Visit URL:', 'Quill link tooltip label', 'sureforms' ),
318 'enter_link' => _x( 'Enter link:', 'Quill link tooltip label', 'sureforms' ),
319 'edit' => _x( 'Edit', 'Quill link tooltip action', 'sureforms' ),
320 'save' => _x( 'Save', 'Quill link tooltip action', 'sureforms' ),
321 'remove' => _x( 'Remove', 'Quill link tooltip action', 'sureforms' ),
322 ]
323 );
324 }
325 }
326 /**
327 * Enqueueing the input mask JS for input and date-picker blocks.
328 * This is a workaround for the input mask JS to work with the date-picker block.
329 * Not adding in the above existing condition because code only runs when free block are added in the form.
330 * Aim is to reduce redundant code and library file duplication.
331 */
332 if ( 'date-picker' === $block_name ) {
333 // Input mask JS.
334 wp_enqueue_script( SRFM_SLUG . '-inputmask', SRFM_URL . 'assets/js/minified/deps/inputmask.min.js', [], SRFM_VER, true );
335 }
336
337 if ( 'payment' === $block_name ) {
338 // Register Stripe.js library from CDN.
339 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Stripe CDN ignores version; version param is included to keep linter happy.
340 wp_enqueue_script(
341 'stripe-js',
342 'https://js.stripe.com/v3/',
343 [],
344 SRFM_VER,
345 true
346 );
347
348 wp_enqueue_script(
349 SRFM_SLUG . '-stripe-payment',
350 SRFM_URL . 'assets/js/stripe-payment.js',
351 [ 'stripe-js' ],
352 SRFM_VER,
353 true
354 );
355
356 // Enqueue Payment Manager for payment method switching.
357 wp_enqueue_script(
358 SRFM_SLUG . '-payment-manager',
359 SRFM_URL . 'assets/js/payment-manager.js',
360 [ SRFM_SLUG . '-stripe-payment' ],
361 SRFM_VER,
362 true
363 );
364
365 // Localize script for Stripe payment functionality.
366 wp_localize_script(
367 SRFM_SLUG . '-stripe-payment',
368 'srfm_ajax',
369 [
370 'ajax_url' => admin_url( 'admin-ajax.php' ),
371 ]
372 );
373
374 // Localize Stripe payment data for frontend.
375 wp_localize_script(
376 SRFM_SLUG . '-stripe-payment',
377 'srfmStripe',
378 [
379 'zeroDecimalCurrencies' => Payment_Helper::get_zero_decimal_currencies(),
380 'currenciesData' => Payment_Helper::get_all_currencies_data(),
381 'strings' => Payment_Helper::get_payment_strings(),
382 'currencySignPosition' => Payment_Helper::get_currency_sign_position(),
383 ]
384 );
385 }
386
387 // Trigger custom action hook to allow third-party plugins or add-ons
388 // to enqueue additional scripts/styles for specific blocks (e.g., payment providers).
389 do_action(
390 'srfm_enqueue_block_scripts',
391 [
392 'block_name' => $block_name,
393 'attr' => $attr,
394 ]
395 );
396 }
397
398 /**
399 * Maps WordPress locale to intl-tel-input language code.
400 *
401 * Converts WordPress locale format (e.g., fr_FR, de_DE, pt_BR)
402 * to intl-tel-input language codes (e.g., fr, de, pt).
403 * Returns null if the language is not supported by intl-tel-input.
404 *
405 * @since 2.5.0
406 * @param string|null $locale WordPress locale string. If null, uses get_locale().
407 * @return string|null Language code if supported, null if not supported or English.
408 */
409 public static function get_intl_tel_input_locale( $locale = null ) {
410 // Get WordPress locale if not provided.
411 if ( null === $locale ) {
412 $locale = get_locale();
413 }
414
415 // Extract language code from WordPress locale (e.g., fr_FR -> fr, pt_BR -> pt).
416 $lang_code = substr( $locale, 0, 2 );
417
418 // List of supported languages - available translation files in /assets/js/minified/deps/intl/i18n/.
419 // Reference: https://unpkg.com/browse/intl-tel-input@24.5.1/build/js/i18n/.
420 $supported_languages = [
421 'de', // German - Deutsch.
422 'es', // Spanish - Español.
423 'fr', // French - Français.
424 'it', // Italian - Italiano.
425 'nl', // Dutch - Nederlands.
426 'pl', // Polish - Polski.
427 'pt', // Portuguese - Português.
428 ];
429
430 // Return language code only if supported and not English (English is default).
431 if ( in_array( $lang_code, $supported_languages, true ) && 'en' !== $lang_code ) {
432 return $lang_code;
433 }
434
435 // Return null for unsupported languages or English (uses default English).
436 return null;
437 }
438
439 /**
440 * Enqueues intl-tel-input i18n script for the phone field.
441 *
442 * This method handles conditional loading of language files based on WordPress locale.
443 * Only enqueues if a supported non-English language is detected and the file exists.
444 *
445 * @since 2.5.0
446 * @param string $handle Script handle to enqueue.
447 * @param string $dependencies Optional. Script handle that this i18n depends on. Default empty.
448 * @return bool True if i18n was enqueued, false otherwise.
449 */
450 public static function enqueue_intl_tel_input_i18n( $handle, $dependencies = '' ) {
451 $intl_locale = self::get_intl_tel_input_locale();
452
453 // Return early if no locale or English.
454 if ( empty( $intl_locale ) ) {
455 return false;
456 }
457
458 $i18n_file_path = SRFM_DIR . "assets/js/minified/deps/intl/i18n/{$intl_locale}/index.min.js";
459
460 // Only enqueue if the language file exists.
461 if ( ! file_exists( $i18n_file_path ) ) {
462 return false;
463 }
464
465 $deps = ! empty( $dependencies ) ? [ $dependencies ] : [];
466
467 wp_enqueue_script(
468 $handle,
469 SRFM_URL . "assets/js/minified/deps/intl/i18n/{$intl_locale}/index.min.js",
470 $deps,
471 SRFM_VER,
472 true
473 );
474
475 return true;
476 }
477
478 /**
479 * Render function.
480 *
481 * @param string $block_content Entire Block Content.
482 * @param array<string> $block Block Properties As An Array.
483 * @return string
484 */
485 public function generate_render_script( $block_content, $block ) {
486
487 if ( isset( $block['attrs']['isEditing'] ) ) {
488 // Only load block assets on the frontend.
489 return $block_content;
490 }
491
492 if ( isset( $block['blockName'] ) ) {
493 $attr = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : [];
494
495 self::enqueue_srfm_script( $block['blockName'], $attr );
496 }
497 return $block_content;
498 }
499
500 /**
501 * Form Template filter.
502 *
503 * @param string $template Template.
504 * @return string Template.
505 * @since 0.0.1
506 */
507 public function page_template( $template ) {
508
509 if ( ! is_singular( SRFM_FORMS_POST_TYPE ) ) {
510 // Bail if not SureForms post type.
511 return $template;
512 }
513
514 $file_name = 'single-form.php';
515 $template = locate_template( $file_name );
516
517 /**
518 * Hook: srfm_form_template filter.
519 *
520 * @since 0.0.1
521 */
522 return apply_filters( 'srfm_form_template', $template ? $template : SRFM_DIR . '/templates/' . $file_name );
523 }
524
525 }
526