PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.11.1
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.11.1
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 0.0.2 0.0.3 All 96 releases
sureforms / inc / frontend-assets.php
frontend-assets.php
516 lines 16.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 ]
150 );
151
152 $current_post = get_post();
153
154 // Let's conditionally load form assets if current requested page has our forms.
155 if ( $current_post instanceof \WP_Post ) {
156 // Handles condition for Instant Form, Block Embedded, and Shortcode Embedded forms.
157 $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' ) ) );
158
159 if ( $load_assets ) {
160 // Load needed styles in head tag if current requested page has SureForms form.
161 self::enqueue_scripts_and_styles();
162 }
163 }
164 }
165
166 /**
167 * Enqueue scripts and styles.
168 *
169 * @return void
170 * @since 0.0.11
171 */
172 public static function enqueue_scripts_and_styles() {
173 // Load the styles.
174 foreach ( self::$css_assets as $handle => $path ) {
175
176 // Skip single form styles if not on single form page.
177 if ( 'single' === $handle && ! is_singular( SRFM_FORMS_POST_TYPE ) ) {
178 continue;
179 }
180
181 wp_enqueue_style( SRFM_SLUG . '-' . $handle );
182 }
183
184 // Load the external styles. Like Phone and Tom Select.
185 foreach ( self::$css_external_assets as $handle => $path ) {
186 wp_enqueue_style( SRFM_SLUG . '-' . $handle );
187 }
188
189 // Load the scripts.
190 foreach ( self::$js_assets as $handle => $path ) {
191 wp_enqueue_script( SRFM_SLUG . '-' . $handle );
192 }
193 }
194
195 /**
196 * Enqueue block scripts
197 *
198 * @param string $block_type block name.
199 * @param array<string, mixed> $attr Array of block attributes.
200 * @since 0.0.1
201 * @return void
202 */
203 public function enqueue_srfm_script( $block_type, $attr ) {
204 $block_name = str_replace( 'srfm/', '', $block_type );
205 // associative array to keep the count of block that requires scripts to work.
206 $script_dep_blocks = [
207 'dropdown' => 0,
208 'multi-choice' => 0,
209 'number' => 0,
210 'textarea' => 0,
211 'url' => 0,
212 'phone' => 0,
213 'input' => 0,
214 ];
215
216 $file_prefix = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? '' : '.min';
217 $dir_name = defined( 'SRFM_DEBUG' ) && SRFM_DEBUG ? 'unminified' : 'minified';
218
219 // Check if block is in the array and check if block is already enqueued.
220 if (
221 in_array( $block_name, array_keys( $script_dep_blocks ), true ) &&
222 0 === $script_dep_blocks[ $block_name ]
223 ) {
224 $script_dep_blocks[ $block_name ] += 1;
225 $js_uri = SRFM_URL . 'assets/js/' . $dir_name . '/blocks/';
226 $js_vendor_uri = SRFM_URL . 'assets/js/minified/deps/';
227 $css_vendor_uri = SRFM_URL . 'assets/css/minified/deps/';
228 if ( 'phone' === $block_name ) {
229 // Enqueue main intl-tel-input library.
230 wp_enqueue_script( SRFM_SLUG . "-{$block_name}-intl-input-deps", $js_vendor_uri . 'intl/intTelInputWithUtils.min.js', [], SRFM_VER, true );
231
232 // Enqueue i18n translations if available for current locale.
233 self::enqueue_intl_tel_input_i18n(
234 SRFM_SLUG . "-{$block_name}-intl-i18n",
235 SRFM_SLUG . "-{$block_name}-intl-input-deps"
236 );
237 }
238
239 if ( 'dropdown' === $block_name ) {
240 // 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.
241 wp_dequeue_script( SRFM_SLUG . '-form-submit' );
242 wp_enqueue_script( SRFM_SLUG . '-dropdown', $js_uri . 'dropdown' . $file_prefix . '.js', [ 'wp-a11y' ], SRFM_VER, true );
243 wp_enqueue_script( SRFM_SLUG . '-tom-select', $js_vendor_uri . 'tom-select.min.js', [], SRFM_VER, true );
244 // frontend utils using dropdown dependency.
245 wp_enqueue_script(
246 SRFM_SLUG . '-form-submit',
247 SRFM_URL . 'assets/build/formSubmit.js',
248 [
249 'srfm-tom-select',
250 'srfm-dropdown',
251 'wp-api-fetch',
252 ],
253 SRFM_VER,
254 true
255 );
256 }
257
258 $is_not_dropdown = 'dropdown' !== $block_name;
259 $is_not_textarea = 'textarea' !== $block_name;
260
261 if ( $is_not_dropdown && $is_not_textarea ) {
262 // Set dependencies for phone block to ensure intl-tel-input loads first.
263 $block_dependencies = [];
264 if ( 'phone' === $block_name ) {
265 // Phone.js depends on intl-tel-input library (and i18n if loaded).
266 $block_dependencies = [ SRFM_SLUG . "-{$block_name}-intl-input-deps" ];
267 }
268 wp_enqueue_script( SRFM_SLUG . "-{$block_name}", $js_uri . $block_name . $file_prefix . '.js', $block_dependencies, SRFM_VER, true );
269
270 if ( 'phone' === $block_name ) {
271 // Geo-country endpoint for per-visitor auto country detection.
272 // Built with rest_url() so it is correct regardless of permalink structure.
273 wp_localize_script(
274 SRFM_SLUG . "-{$block_name}",
275 'srfm_phone_data',
276 [
277 'geo_endpoint' => esc_url_raw( rest_url( 'sureforms/v1/geo-country' ) ),
278 ]
279 );
280 }
281 }
282
283 if ( 'input' === $block_name && isset( $attr['inputMask'] ) && 'none' !== $attr['inputMask'] ) {
284 // Input mask JS - only load when inputMask is configured.
285 wp_enqueue_script( SRFM_SLUG . '-inputmask', $js_vendor_uri . 'inputmask.min.js', [], SRFM_VER, true );
286 }
287
288 // Adding js for the input textarea block.
289 if ( 'textarea' === $block_name && ! empty( $attr['isRichText'] ) ) {
290 wp_enqueue_script( SRFM_SLUG . '-quill-editor', $js_vendor_uri . '/quill.min.js', [], SRFM_VER, true );
291
292 wp_enqueue_style( SRFM_SLUG . '-quill-editor', $css_vendor_uri . 'quill/quill.snow.css', [], SRFM_VER );
293
294 wp_enqueue_script( SRFM_SLUG . '-textarea', $js_uri . 'textarea' . $file_prefix . '.js', [], SRFM_VER, true );
295
296 wp_localize_script(
297 SRFM_SLUG . '-textarea',
298 'srfm_quill_i18n',
299 [
300 'normal' => _x( 'Normal', 'Quill heading picker: default paragraph style', 'sureforms' ),
301 'heading_1' => __( 'Heading 1', 'sureforms' ),
302 'heading_2' => __( 'Heading 2', 'sureforms' ),
303 'heading_3' => __( 'Heading 3', 'sureforms' ),
304 'heading_4' => __( 'Heading 4', 'sureforms' ),
305 'heading_5' => __( 'Heading 5', 'sureforms' ),
306 'heading_6' => __( 'Heading 6', 'sureforms' ),
307 'visit_url' => _x( 'Visit URL:', 'Quill link tooltip label', 'sureforms' ),
308 'enter_link' => _x( 'Enter link:', 'Quill link tooltip label', 'sureforms' ),
309 'edit' => _x( 'Edit', 'Quill link tooltip action', 'sureforms' ),
310 'save' => _x( 'Save', 'Quill link tooltip action', 'sureforms' ),
311 'remove' => _x( 'Remove', 'Quill link tooltip action', 'sureforms' ),
312 ]
313 );
314 }
315 }
316 /**
317 * Enqueueing the input mask JS for input and date-picker blocks.
318 * This is a workaround for the input mask JS to work with the date-picker block.
319 * Not adding in the above existing condition because code only runs when free block are added in the form.
320 * Aim is to reduce redundant code and library file duplication.
321 */
322 if ( 'date-picker' === $block_name ) {
323 // Input mask JS.
324 wp_enqueue_script( SRFM_SLUG . '-inputmask', SRFM_URL . 'assets/js/minified/deps/inputmask.min.js', [], SRFM_VER, true );
325 }
326
327 if ( 'payment' === $block_name ) {
328 // Register Stripe.js library from CDN.
329 // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Stripe CDN ignores version; version param is included to keep linter happy.
330 wp_enqueue_script(
331 'stripe-js',
332 'https://js.stripe.com/v3/',
333 [],
334 SRFM_VER,
335 true
336 );
337
338 wp_enqueue_script(
339 SRFM_SLUG . '-stripe-payment',
340 SRFM_URL . 'assets/js/stripe-payment.js',
341 [ 'stripe-js' ],
342 SRFM_VER,
343 true
344 );
345
346 // Enqueue Payment Manager for payment method switching.
347 wp_enqueue_script(
348 SRFM_SLUG . '-payment-manager',
349 SRFM_URL . 'assets/js/payment-manager.js',
350 [ SRFM_SLUG . '-stripe-payment' ],
351 SRFM_VER,
352 true
353 );
354
355 // Localize script for Stripe payment functionality.
356 wp_localize_script(
357 SRFM_SLUG . '-stripe-payment',
358 'srfm_ajax',
359 [
360 'ajax_url' => admin_url( 'admin-ajax.php' ),
361 ]
362 );
363
364 // Localize Stripe payment data for frontend.
365 wp_localize_script(
366 SRFM_SLUG . '-stripe-payment',
367 'srfmStripe',
368 [
369 'zeroDecimalCurrencies' => Payment_Helper::get_zero_decimal_currencies(),
370 'currenciesData' => Payment_Helper::get_all_currencies_data(),
371 'strings' => Payment_Helper::get_payment_strings(),
372 'currencySignPosition' => Payment_Helper::get_currency_sign_position(),
373 ]
374 );
375 }
376
377 // Trigger custom action hook to allow third-party plugins or add-ons
378 // to enqueue additional scripts/styles for specific blocks (e.g., payment providers).
379 do_action(
380 'srfm_enqueue_block_scripts',
381 [
382 'block_name' => $block_name,
383 'attr' => $attr,
384 ]
385 );
386 }
387
388 /**
389 * Maps WordPress locale to intl-tel-input language code.
390 *
391 * Converts WordPress locale format (e.g., fr_FR, de_DE, pt_BR)
392 * to intl-tel-input language codes (e.g., fr, de, pt).
393 * Returns null if the language is not supported by intl-tel-input.
394 *
395 * @since 2.5.0
396 * @param string|null $locale WordPress locale string. If null, uses get_locale().
397 * @return string|null Language code if supported, null if not supported or English.
398 */
399 public static function get_intl_tel_input_locale( $locale = null ) {
400 // Get WordPress locale if not provided.
401 if ( null === $locale ) {
402 $locale = get_locale();
403 }
404
405 // Extract language code from WordPress locale (e.g., fr_FR -> fr, pt_BR -> pt).
406 $lang_code = substr( $locale, 0, 2 );
407
408 // List of supported languages - available translation files in /assets/js/minified/deps/intl/i18n/.
409 // Reference: https://unpkg.com/browse/intl-tel-input@24.5.1/build/js/i18n/.
410 $supported_languages = [
411 'de', // German - Deutsch.
412 'es', // Spanish - Español.
413 'fr', // French - Français.
414 'it', // Italian - Italiano.
415 'nl', // Dutch - Nederlands.
416 'pl', // Polish - Polski.
417 'pt', // Portuguese - Português.
418 ];
419
420 // Return language code only if supported and not English (English is default).
421 if ( in_array( $lang_code, $supported_languages, true ) && 'en' !== $lang_code ) {
422 return $lang_code;
423 }
424
425 // Return null for unsupported languages or English (uses default English).
426 return null;
427 }
428
429 /**
430 * Enqueues intl-tel-input i18n script for the phone field.
431 *
432 * This method handles conditional loading of language files based on WordPress locale.
433 * Only enqueues if a supported non-English language is detected and the file exists.
434 *
435 * @since 2.5.0
436 * @param string $handle Script handle to enqueue.
437 * @param string $dependencies Optional. Script handle that this i18n depends on. Default empty.
438 * @return bool True if i18n was enqueued, false otherwise.
439 */
440 public static function enqueue_intl_tel_input_i18n( $handle, $dependencies = '' ) {
441 $intl_locale = self::get_intl_tel_input_locale();
442
443 // Return early if no locale or English.
444 if ( empty( $intl_locale ) ) {
445 return false;
446 }
447
448 $i18n_file_path = SRFM_DIR . "assets/js/minified/deps/intl/i18n/{$intl_locale}/index.min.js";
449
450 // Only enqueue if the language file exists.
451 if ( ! file_exists( $i18n_file_path ) ) {
452 return false;
453 }
454
455 $deps = ! empty( $dependencies ) ? [ $dependencies ] : [];
456
457 wp_enqueue_script(
458 $handle,
459 SRFM_URL . "assets/js/minified/deps/intl/i18n/{$intl_locale}/index.min.js",
460 $deps,
461 SRFM_VER,
462 true
463 );
464
465 return true;
466 }
467
468 /**
469 * Render function.
470 *
471 * @param string $block_content Entire Block Content.
472 * @param array<string> $block Block Properties As An Array.
473 * @return string
474 */
475 public function generate_render_script( $block_content, $block ) {
476
477 if ( isset( $block['attrs']['isEditing'] ) ) {
478 // Only load block assets on the frontend.
479 return $block_content;
480 }
481
482 if ( isset( $block['blockName'] ) ) {
483 $attr = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : [];
484
485 self::enqueue_srfm_script( $block['blockName'], $attr );
486 }
487 return $block_content;
488 }
489
490 /**
491 * Form Template filter.
492 *
493 * @param string $template Template.
494 * @return string Template.
495 * @since 0.0.1
496 */
497 public function page_template( $template ) {
498
499 if ( ! is_singular( SRFM_FORMS_POST_TYPE ) ) {
500 // Bail if not SureForms post type.
501 return $template;
502 }
503
504 $file_name = 'single-form.php';
505 $template = locate_template( $file_name );
506
507 /**
508 * Hook: srfm_form_template filter.
509 *
510 * @since 0.0.1
511 */
512 return apply_filters( 'srfm_form_template', $template ? $template : SRFM_DIR . '/templates/' . $file_name );
513 }
514
515 }
516