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