PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.9
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.9
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / helpers / FrmAppHelper.php

FrmAppHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.9, at classes/helpers/FrmAppHelper.php

4,241 lines 118.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmAppHelper {
7 /**
8 * Version of the database we are moving to.
9 *
10 * @var int
11 */
12 public static $db_version = 101;
13
14 /**
15 * Deprecated.
16 *
17 * @var int
18 */
19 public static $pro_db_version = 37;
20
21 /**
22 * @var float
23 */
24 public static $font_version = 7;
25
26 /**
27 * @var bool $added_gmt_offset_filter
28 */
29 private static $added_gmt_offset_filter = false;
30
31 /**
32 * @since 2.0
33 *
34 * @var string
35 */
36 public static $plug_version = '6.9';
37
38 /**
39 * @var bool
40 */
41 private static $included_svg = false;
42
43 /**
44 * @since 1.07.02
45 *
46 * @return string The version of this plugin
47 */
48 public static function plugin_version() {
49 return self::$plug_version;
50 }
51
52 public static function plugin_folder() {
53 return basename( self::plugin_path() );
54 }
55
56 public static function plugin_path() {
57 return dirname( dirname( __DIR__ ) );
58 }
59
60 public static function plugin_url() {
61 // Prevously FRM_URL constant.
62 return plugins_url( '', self::plugin_path() . '/formidable.php' );
63 }
64
65 public static function relative_plugin_url() {
66 return str_replace( array( 'https:', 'http:' ), '', self::plugin_url() );
67 }
68
69 /**
70 * @return string Site URL
71 */
72 public static function site_url() {
73 return site_url();
74 }
75
76 /**
77 * Get the name of this site
78 * Used for [sitename] shortcode
79 *
80 * @since 2.0
81 * @return string
82 */
83 public static function site_name() {
84 return get_option( 'blogname' );
85 }
86
87 public static function make_affiliate_url( $url ) {
88 $affiliate_id = self::get_affiliate();
89 if ( ! empty( $affiliate_id ) ) {
90 $url = str_replace( array( 'http://', 'https://' ), '', $url );
91 $url = 'http://www.shareasale.com/r.cfm?u=' . absint( $affiliate_id ) . '&b=841990&m=64739&afftrack=plugin&urllink=' . urlencode( $url );
92 }
93
94 return $url;
95 }
96
97 public static function get_affiliate() {
98 return absint( apply_filters( 'frm_affiliate_id', 0 ) );
99 }
100
101 /**
102 * @since 3.04.02
103 * @param array|string $args
104 * @param string $page
105 */
106 public static function admin_upgrade_link( $args, $page = '' ) {
107 if ( empty( $page ) ) {
108 $page = 'https://formidableforms.com/lite-upgrade/';
109 } else {
110 $page = str_replace( 'https://formidableforms.com/', '', $page );
111 $page = 'https://formidableforms.com/' . $page;
112 }
113
114 $anchor = '';
115 if ( is_array( $args ) ) {
116 $medium = $args['medium'];
117 if ( isset( $args['content'] ) ) {
118 $content = $args['content'];
119 }
120 if ( isset( $args['anchor'] ) ) {
121 $anchor = '#' . $args['anchor'];
122 }
123 } else {
124 $medium = $args;
125 }
126
127 $query_args = array(
128 'utm_source' => 'WordPress',
129 'utm_medium' => $medium,
130 'utm_campaign' => 'liteplugin',
131 );
132
133 if ( isset( $content ) ) {
134 $query_args['utm_content'] = $content;
135 }
136
137 if ( is_array( $args ) && isset( $args['param'] ) ) {
138 $query_args['f'] = $args['param'];
139 }
140
141 if ( is_array( $args ) && ! empty( $args['plan'] ) ) {
142 $query_args['plan'] = $args['plan'];
143 }
144
145 $link = add_query_arg( $query_args, $page ) . $anchor;
146 return self::make_affiliate_url( $link );
147 }
148
149 /**
150 * Get the Formidable settings
151 *
152 * @since 2.0
153 *
154 * @param array $args - May include the form id when values need translation.
155 * @return FrmSettings $frm_setings
156 */
157 public static function get_settings( $args = array() ) {
158 global $frm_settings;
159 if ( empty( $frm_settings ) ) {
160 $frm_settings = new FrmSettings( $args );
161 } elseif ( isset( $args['current_form'] ) ) {
162 // If the global has already been set, allow strings to be filtered.
163 $frm_settings->maybe_filter_for_form( $args );
164 }
165
166 return $frm_settings;
167 }
168
169 /**
170 * @return string
171 */
172 public static function get_menu_name() {
173 $frm_settings = self::get_settings();
174
175 return FrmAddonsController::is_license_expired() ? 'Formidable' : $frm_settings->menu;
176 }
177
178 /**
179 * Determine if the current branding is set to 'formidable'.
180 * Checks the menu icon, and verifies if it matches the formidable branding.
181 *
182 * @since 6.4.2
183 *
184 * @return bool True if the menu icon is the logo, false otherwise.
185 */
186 public static function is_formidable_branding() {
187 if ( ! self::pro_is_installed() ) {
188 return true;
189 }
190
191 $menu_icon = self::get_menu_icon_class();
192 return strpos( $menu_icon, 'frm_logo_icon' ) !== false;
193 }
194
195 /**
196 * @since 3.05
197 */
198 public static function svg_logo( $atts = array() ) {
199 $defaults = array(
200 'height' => 18,
201 'width' => 18,
202 'fill' => '#4d4d4d',
203 'orange' => '#f05a24',
204 );
205 $atts = array_merge( $defaults, $atts );
206
207 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 599.68 601.37" width="' . esc_attr( $atts['width'] ) . '" height="' . esc_attr( $atts['height'] ) . '">
208 <path fill="' . esc_attr( $atts['orange'] ) . '" d="M289.6 384h140v76h-140z"/>
209 <path fill="' . esc_attr( $atts['fill'] ) . '" d="M400.2 147h-200c-17 0-30.6 12.2-30.6 29.3V218h260v-71zM397.9 264H169.6v196h75V340H398a32.2 32.2 0 0 0 30.1-21.4 24.3 24.3 0 0 0 1.7-8.7V264zM299.8 601.4A300.3 300.3 0 0 1 0 300.7a299.8 299.8 0 1 1 511.9 212.6 297.4 297.4 0 0 1-212 88zm0-563A262 262 0 0 0 38.3 300.7a261.6 261.6 0 1 0 446.5-185.5 259.5 259.5 0 0 0-185-76.8z"/>
210 </svg>';
211 }
212
213 /**
214 * @since 4.0
215 */
216 public static function show_logo( $atts = array() ) {
217 echo self::kses( self::svg_logo( $atts ), 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
218 }
219
220 /**
221 * @since 4.03.02
222 */
223 public static function show_header_logo() {
224 $icon = self::svg_logo(
225 array(
226 'height' => 35,
227 'width' => 35,
228 )
229 );
230
231 $new_icon = apply_filters( 'frm_icon', $icon, true );
232 if ( $new_icon !== $icon ) {
233 if ( strpos( $new_icon, '<svg' ) === 0 ) {
234 $icon = str_replace( 'viewBox="0 0 20', 'width="30" height="35" style="color:#929699" viewBox="0 0 20', $new_icon );
235 } else {
236 // Show nothing if it isn't an SVG.
237 $icon = '<div style="height:39px"></div>';
238 }
239 }
240 echo self::kses( $icon, 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
241 }
242
243 /**
244 * @since 2.02.04
245 */
246 public static function ips_saved() {
247 $frm_settings = self::get_settings();
248
249 return ! $frm_settings->no_ips;
250 }
251
252 public static function pro_is_installed() {
253 return apply_filters( 'frm_pro_installed', false );
254 }
255
256 /**
257 * Check if the Pro plugin is installed, whether authorized or not.
258 *
259 * @since 6.8.3
260 *
261 * @return bool
262 */
263 public static function pro_is_included() {
264 return function_exists( 'load_formidable_pro' );
265 }
266
267 /**
268 * @since 4.06.02
269 */
270 public static function pro_is_connected() {
271 global $frm_vars;
272 return self::pro_is_installed() && $frm_vars['pro_is_authorized'];
273 }
274
275 /**
276 * @since 4.06
277 */
278 public static function is_form_builder_page() {
279 $action = self::simple_get( 'frm_action', 'sanitize_title' );
280 return self::is_admin_page( 'formidable' ) && ( $action === 'edit' || $action === 'settings' || $action === 'duplicate' );
281 }
282
283 public static function is_formidable_admin() {
284 $page = self::simple_get( 'page', 'sanitize_title' );
285 $is_formidable = strpos( $page, 'formidable' ) !== false;
286 if ( empty( $page ) ) {
287 $is_formidable = self::is_view_builder_page();
288 }
289
290 return $is_formidable;
291 }
292
293 /**
294 * Check for certain page in Formidable settings
295 *
296 * @since 2.0
297 *
298 * @param string $page The name of the page to check.
299 *
300 * @return boolean
301 */
302 public static function is_admin_page( $page = 'formidable' ) {
303 global $pagenow;
304 $get_page = self::simple_get( 'page', 'sanitize_title' );
305 if ( $pagenow ) {
306 // allow this to be true during ajax load i.e. ajax form builder loading
307 $is_page = ( $pagenow === 'admin.php' || $pagenow === 'admin-ajax.php' ) && $get_page === $page;
308 if ( $is_page ) {
309 return true;
310 }
311 }
312
313 return is_admin() && $get_page === $page;
314 }
315
316 /**
317 * If the current page is for editing or creating a view.
318 * Returns false for the views listing page.
319 *
320 * @since 4.0
321 */
322 public static function is_view_builder_page() {
323 global $pagenow;
324
325 if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' && $pagenow !== 'edit.php' ) {
326 return false;
327 }
328
329 $post_type = self::simple_get( 'post_type', 'sanitize_title' );
330
331 if ( empty( $post_type ) ) {
332 $post_id = self::simple_get( 'post', 'absint' );
333 $post = get_post( $post_id );
334 $post_type = $post ? $post->post_type : '';
335 }
336
337 return $post_type === 'frm_display';
338 }
339
340 /**
341 * Check for the form preview page
342 *
343 * @since 2.0
344 *
345 * @return boolean
346 */
347 public static function is_preview_page() {
348 global $pagenow;
349 $action = self::simple_get( 'action', 'sanitize_title' );
350
351 return $pagenow && $pagenow == 'admin-ajax.php' && $action == 'frm_forms_preview';
352 }
353
354 /**
355 * Check for ajax except the form preview page
356 *
357 * @since 2.0
358 *
359 * @return boolean
360 */
361 public static function doing_ajax() {
362 return wp_doing_ajax() && ! self::is_preview_page();
363 }
364
365 public static function js_suffix() {
366 return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
367 }
368
369 /**
370 * @since 2.0.8
371 */
372 public static function prevent_caching() {
373 global $frm_vars;
374
375 return isset( $frm_vars['prevent_caching'] ) && $frm_vars['prevent_caching'];
376 }
377
378 /**
379 * Check if on an admin page
380 *
381 * @since 2.0
382 *
383 * @return bool
384 */
385 public static function is_admin() {
386 $is_admin = is_admin() && ! wp_doing_ajax();
387
388 /**
389 * @since 6.0
390 * @param bool $is_admin
391 */
392 return apply_filters( 'frm_is_admin', $is_admin );
393 }
394
395 /**
396 * Check if value contains blank value or empty array
397 *
398 * @since 2.0
399 *
400 * @param mixed $value Value to check.
401 * @param string $empty
402 *
403 * @return boolean
404 */
405 public static function is_empty_value( $value, $empty = '' ) {
406 return ( is_array( $value ) && empty( $value ) ) || $value === $empty;
407 }
408
409 public static function is_not_empty_value( $value, $empty = '' ) {
410 return ! self::is_empty_value( $value, $empty );
411 }
412
413 /**
414 * Get any value from the $_SERVER
415 *
416 * @since 2.0
417 *
418 * @param string $value
419 *
420 * @return string
421 */
422 public static function get_server_value( $value ) {
423 return isset( $_SERVER[ $value ] ) ? wp_strip_all_tags( wp_unslash( $_SERVER[ $value ] ) ) : '';
424 }
425
426 /**
427 * Get the server OS
428 *
429 * @since 6.4.2
430 *
431 * @return string
432 */
433 public static function get_server_os() {
434 if ( function_exists( 'php_uname' ) ) {
435 return php_uname( 's' );
436 }
437
438 if ( ! defined( 'PHP_OS' ) ) {
439 return '';
440 }
441
442 // match the same response for Windows server as php_uname('s')
443 return in_array( PHP_OS, array( 'WIN32', 'WINNT', 'Windows_NT' ), true ) ? 'Windows NT' : PHP_OS;
444 }
445
446 /**
447 * Check for the IP address in several places (when custom headers are enabled).
448 * Used by [ip] shortcode.
449 *
450 * @return string The IP address of the current user
451 */
452 public static function get_ip_address() {
453 $ip_options = self::should_use_custom_header_ip() ? self::get_custom_header_keys_for_ip() : array( 'REMOTE_ADDR' );
454 $ip = '';
455
456 foreach ( $ip_options as $key ) {
457 if ( ! isset( $_SERVER[ $key ] ) ) {
458 continue;
459 }
460
461 $key = self::get_server_value( $key );
462 foreach ( explode( ',', $key ) as $ip ) {
463 // Just to be safe.
464 $ip = trim( $ip );
465
466 if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false ) {
467 return sanitize_text_field( $ip );
468 }
469 }
470 }
471
472 return sanitize_text_field( $ip );
473 }
474
475 /**
476 * @since 6.1
477 *
478 * @return array
479 */
480 public static function get_custom_header_keys_for_ip() {
481 return array(
482 'HTTP_CLIENT_IP',
483 'HTTP_CF_CONNECTING_IP',
484 'HTTP_X_FORWARDED_FOR',
485 'HTTP_X_FORWARDED',
486 'HTTP_X_CLUSTER_CLIENT_IP',
487 'HTTP_X_REAL_IP',
488 'HTTP_FORWARDED_FOR',
489 'HTTP_FORWARDED',
490 'REMOTE_ADDR',
491 );
492 }
493
494 /**
495 * Check if we should check every HTTP header or just $_SERVER['REMOTE_ADDR'].
496 * The other HTTP headers can be spoofed so this isn't recommended.
497 * But in some cases (like reverse proxies), the IP may be empty if you use $_SERVER['REMOTE_ADDR'].
498 *
499 * @since 6.1
500 *
501 * @return bool
502 */
503 private static function should_use_custom_header_ip() {
504 $settings = self::get_settings();
505 $should_use_custom_header_ip = ! $settings->no_ips && $settings->custom_header_ip;
506
507 /**
508 * Filter whether to check spoofable HTTP headers.
509 * This uses the custom_header_ip setting, but it is hidden if the GDPR option is also on.
510 * As the IP is still checked for blacklist checks, someone with the GDPR option may still want to enable this when behind a reverse proxy.
511 *
512 * @since 6.1
513 *
514 * @param bool $should_use_custom_header_ip
515 */
516 return apply_filters( 'frm_use_custom_header_ip', $should_use_custom_header_ip );
517 }
518
519 public static function get_param( $param, $default = '', $src = 'get', $sanitize = '' ) {
520 if ( strpos( $param, '[' ) ) {
521 $params = explode( '[', $param );
522 $param = $params[0];
523 }
524
525 if ( $src === 'get' ) {
526 $value = isset( $_POST[ $param ] ) ? wp_unslash( $_POST[ $param ] ) : ( isset( $_GET[ $param ] ) ? wp_unslash( $_GET[ $param ] ) : $default ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
527 if ( ! isset( $_POST[ $param ] ) && isset( $_GET[ $param ] ) && ! is_array( $value ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
528 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
529 $value = htmlspecialchars_decode( wp_unslash( $_GET[ $param ] ) );
530 }
531 self::sanitize_value( $sanitize, $value );
532 } else {
533 $value = self::get_simple_request(
534 array(
535 'type' => $src,
536 'param' => $param,
537 'default' => $default,
538 'sanitize' => $sanitize,
539 )
540 );
541 }
542
543 if ( isset( $params ) && is_array( $value ) && ! empty( $value ) ) {
544 foreach ( $params as $k => $p ) {
545 if ( ! $k || ! is_array( $value ) ) {
546 continue;
547 }
548
549 $p = trim( $p, ']' );
550 $value = isset( $value[ $p ] ) ? $value[ $p ] : $default;
551 }
552 }
553
554 return $value;
555 }
556
557 public static function get_post_param( $param, $default = '', $sanitize = '', $serialized = false ) {
558 return self::get_simple_request(
559 array(
560 'type' => 'post',
561 'param' => $param,
562 'default' => $default,
563 'sanitize' => $sanitize,
564 'serialized' => $serialized,
565 )
566 );
567 }
568
569 /**
570 * @since 2.0
571 *
572 * @param string $param
573 * @param string $sanitize
574 * @param string $default
575 *
576 * @return string|array
577 */
578 public static function simple_get( $param, $sanitize = 'sanitize_text_field', $default = '' ) {
579 return self::get_simple_request(
580 array(
581 'type' => 'get',
582 'param' => $param,
583 'default' => $default,
584 'sanitize' => $sanitize,
585 )
586 );
587 }
588
589 /**
590 * Get a GET/POST/REQUEST value and sanitize it
591 *
592 * @since 2.0.6
593 *
594 * @param array $args
595 *
596 * @return string|array
597 */
598 public static function get_simple_request( $args ) {
599 $defaults = array(
600 'param' => '',
601 'default' => '',
602 'type' => 'get',
603 'sanitize' => 'sanitize_text_field',
604 'serialized' => false,
605 );
606 $args = wp_parse_args( $args, $defaults );
607
608 $value = $args['default'];
609 if ( $args['type'] === 'get' ) {
610 if ( $_GET && isset( $_GET[ $args['param'] ] ) ) {
611 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
612 $value = wp_unslash( $_GET[ $args['param'] ] );
613 }
614 } elseif ( $args['type'] === 'post' ) {
615 if ( isset( $_POST[ $args['param'] ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
616 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
617 $value = wp_unslash( $_POST[ $args['param'] ] );
618 if ( $args['serialized'] === true && is_serialized_string( $value ) && is_serialized( $value ) ) {
619 self::unserialize_or_decode( $value );
620 }
621 }
622 } elseif ( isset( $_REQUEST[ $args['param'] ] ) ) {
623 // phpcs:ignore WordPress.Security.NonceVerification.Missing
624 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
625 $value = wp_unslash( $_REQUEST[ $args['param'] ] );
626 }
627
628 self::sanitize_value( $args['sanitize'], $value );
629
630 return $value;
631 }
632
633 /**
634 * Preserve backslashes in a value, but make sure value doesn't get compounding slashes
635 *
636 * @since 2.0.8
637 *
638 * @param string $value
639 *
640 * @return string $value
641 */
642 public static function preserve_backslashes( $value ) {
643 // If backslashes have already been added, don't add them again
644 if ( strpos( $value, '\\\\' ) === false ) {
645 $value = addslashes( $value );
646 }
647
648 return $value;
649 }
650
651 /**
652 * Sanitize a value in-place.
653 * If $value is an array, the sanitize function will get called for each item.
654 *
655 * @param callable $sanitize
656 * @param mixed $value
657 * @return void
658 */
659 public static function sanitize_value( $sanitize, &$value ) {
660 if ( ! $sanitize ) {
661 return;
662 }
663
664 if ( is_object( $value ) ) {
665 $value = '';
666 return;
667 }
668
669 if ( is_array( $value ) ) {
670 $temp_values = $value;
671 foreach ( $temp_values as $k => $v ) {
672 self::sanitize_value( $sanitize, $value[ $k ] );
673 }
674 return;
675 }
676
677 $value = call_user_func( $sanitize, $value );
678 }
679
680 public static function sanitize_request( $sanitize_method, &$values ) {
681 $temp_values = $values;
682 foreach ( $temp_values as $k => $val ) {
683 if ( isset( $sanitize_method[ $k ] ) ) {
684 $values[ $k ] = call_user_func( $sanitize_method[ $k ], $val );
685 }
686 }
687 }
688
689 /**
690 * @since 4.0.04
691 *
692 * @param mixed $value
693 * @return void
694 */
695 public static function sanitize_with_html( &$value ) {
696 if ( current_user_can( 'frm_edit_entries' ) || current_user_can( 'administrator' ) ) {
697 // Only strip unsafe HTML like scripts for a privileged user submitting a form.
698 self::sanitize_value( 'wp_kses_post', $value );
699 } else {
700 self::sanitize_value( self::class . '::strip_most_html', $value );
701 }
702 self::decode_specialchars( $value );
703 }
704
705 /**
706 * Allow only a small set of very basic HTML for unprivileged users.
707 *
708 * @since 6.7.1
709 *
710 * @param string $value
711 */
712 public static function strip_most_html( $value ) {
713 $allowed_html = array(
714 'b' => array(),
715 'br' => array(),
716 'strong' => array(),
717 'p' => array(),
718 'i' => array(),
719 'ul' => array(),
720 'ol' => array(),
721 'li' => array(),
722 );
723
724 /**
725 * @since 6.7.1
726 *
727 * @param array $allowed_html
728 */
729 $allowed_html = apply_filters( 'frm_allowed_form_input_html', $allowed_html );
730
731 return wp_kses( $value, $allowed_html );
732 }
733
734 /**
735 * Do wp_specialchars_decode to get back '&' that wp_kses_post might have turned to '&amp;'
736 * this MUST be done, else we'll be back to the '& entity' problem.
737 *
738 * @since 4.0.04
739 */
740 public static function decode_specialchars( &$value ) {
741 if ( is_array( $value ) ) {
742 $temp_values = $value;
743 foreach ( $temp_values as $k => $v ) {
744 self::decode_specialchars( $value[ $k ] );
745 }
746 } else {
747 self::decode_amp( $value );
748 }
749 }
750
751 /**
752 * The wp_specialchars_decode function changes too much.
753 * This will leave HTML as is, but still convert &.
754 * Adapted from wp_specialchars_decode().
755 *
756 * @since 4.03.01
757 *
758 * @param string $string The string to prep.
759 */
760 private static function decode_amp( &$string ) {
761 // Don't bother if there are no entities - saves a lot of processing
762 if ( empty( $string ) || strpos( $string, '&' ) === false ) {
763 return;
764 }
765
766 $translation = array(
767 '&quot;' => '"',
768 '&#034;' => '"',
769 '&#x22;' => '"',
770 // The space preserves the HTML.
771 '&lt; ' => '< ',
772 // The space preserves the HTML.
773 '&#060; ' => '< ',
774 '&gt;' => '>',
775 '&#062;' => '>',
776 '&amp;' => '&',
777 '&#038;' => '&',
778 '&#x26;' => '&',
779 );
780
781 $translation_preg = array(
782 '/&#0*34;/' => '&#034;',
783 '/&#x0*22;/i' => '&#x22;',
784 '/&#0*60;/' => '&#060;',
785 '/&#0*62;/' => '&#062;',
786 '/&#0*38;/' => '&#038;',
787 '/&#x0*26;/i' => '&#x26;',
788 );
789
790 // Remove zero padding on numeric entities
791 $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );
792
793 // Replace characters according to translation table
794 $string = strtr( $string, $translation );
795 }
796
797 /**
798 * Sanitize the value, and allow some HTML
799 *
800 * @since 2.0
801 *
802 * @param string $value
803 * @param array|string $allowed 'all' for everything included as defaults.
804 *
805 * @return string
806 */
807 public static function kses( $value, $allowed = array() ) {
808 $allowed_html = self::allowed_html( $allowed );
809
810 return wp_kses( $value, $allowed_html );
811 }
812
813 /**
814 * The regular kses function strips [button_action] from submit button HTML.
815 *
816 * @since 5.0.13
817 *
818 * @param string $html
819 * @return string
820 */
821 public static function kses_submit_button( $html ) {
822 $included_button_action = false !== strpos( $html, '[button_action]' );
823 $included_back_hook = false !== strpos( $html, '[back_hook]' );
824 $included_draft_hook = false !== strpos( $html, '[draft_hook]' );
825 add_filter( 'safe_style_css', 'FrmAppHelper::allow_visibility_style' );
826 add_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_submit_button_tags' );
827 $html = self::kses( $html, 'all' );
828 remove_filter( 'safe_style_css', 'FrmAppHelper::allow_visibility_style' );
829 remove_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_submit_button_tags' );
830 if ( $included_button_action ) {
831 if ( false !== strpos( $html, '<input type="submit"' ) ) {
832 $pattern = '/(<input type="submit")([^>]*)(\/>)/';
833 $html = preg_replace( $pattern, '$1$2[button_action] $3', $html, 1 );
834 } else {
835 $pattern = '/(<button)(.*)(class=")(.*)(frm_button_submit)(.*)(")(.*)([^>]+)(>)/';
836 $html = preg_replace( $pattern, '$1$2$3$4$5$6$7 [button_action]$8$9$10', $html, 1 );
837 }
838 }
839 if ( $included_back_hook ) {
840 $html = str_replace( 'class="frm_prev_page"', 'class="frm_prev_page" [back_hook]', $html );
841 }
842 if ( $included_draft_hook ) {
843 $html = str_replace( 'class="frm_save_draft"', 'class="frm_save_draft" [draft_hook]', $html );
844 }
845 return $html;
846 }
847
848 /**
849 * @since 5.0.13
850 *
851 * @param array $allowed_attr
852 * @return array
853 */
854 public static function allow_visibility_style( $allowed_attr ) {
855 $allowed_attr[] = 'visibility';
856 return $allowed_attr;
857 }
858
859 /**
860 * @since 5.0.13
861 *
862 * @param array $allowed_html
863 * @return array
864 */
865 public static function add_allowed_submit_button_tags( $allowed_html ) {
866 $allowed_html['input'] = array(
867 'type' => true,
868 'value' => true,
869 'formnovalidate' => true,
870 'name' => true,
871 'class' => true,
872 );
873 $allowed_html['button']['formnovalidate'] = true;
874 $allowed_html['button']['name'] = true;
875 $allowed_html['img']['style'] = true;
876 return $allowed_html;
877 }
878
879 /**
880 * @since 2.05.03
881 */
882 private static function allowed_html( $allowed ) {
883 $html = self::safe_html();
884 $allowed_html = array();
885 if ( $allowed === 'all' ) {
886 $allowed_html = $html;
887 } elseif ( ! empty( $allowed ) ) {
888 foreach ( (array) $allowed as $a ) {
889 $allowed_html[ $a ] = isset( $html[ $a ] ) ? $html[ $a ] : array();
890 }
891 }
892
893 return apply_filters( 'frm_striphtml_allowed_tags', $allowed_html );
894 }
895
896 /**
897 * @since 2.05.03
898 */
899 private static function safe_html() {
900 $allow_class = array(
901 'class' => true,
902 'id' => true,
903 );
904
905 return array(
906 'a' => array(
907 'class' => true,
908 'href' => true,
909 'id' => true,
910 'rel' => true,
911 'target' => true,
912 'title' => true,
913 'tabindex' => true,
914 ),
915 'abbr' => array(
916 'title' => true,
917 ),
918 'aside' => $allow_class,
919 'b' => array(),
920 'blockquote' => array(
921 'cite' => true,
922 ),
923 'br' => array(),
924 'cite' => array(
925 'title' => true,
926 ),
927 'code' => array(),
928 'defs' => array(),
929 'del' => array(
930 'datetime' => true,
931 'title' => true,
932 ),
933 'dd' => array(),
934 'div' => array(
935 'class' => true,
936 'id' => true,
937 'title' => true,
938 'style' => true,
939 'role' => true,
940 ),
941 'dl' => array(),
942 'dt' => array(),
943 'em' => array(),
944 'h1' => $allow_class,
945 'h2' => $allow_class,
946 'h3' => $allow_class,
947 'h4' => $allow_class,
948 'h5' => $allow_class,
949 'h6' => $allow_class,
950 'i' => array(
951 'class' => true,
952 'id' => true,
953 'icon' => true,
954 'style' => true,
955 ),
956 'img' => array(
957 'alt' => true,
958 'class' => true,
959 'height' => true,
960 'id' => true,
961 'src' => true,
962 'width' => true,
963 ),
964 'li' => $allow_class,
965 'ol' => $allow_class,
966 'p' => $allow_class,
967 'path' => array(
968 'd' => true,
969 'fill' => true,
970 ),
971 'pre' => array(),
972 'q' => array(
973 'cite' => true,
974 'title' => true,
975 ),
976 'rect' => array(
977 'class' => true,
978 'fill' => true,
979 'height' => true,
980 'width' => true,
981 'x' => true,
982 'y' => true,
983 'rx' => true,
984 'stroke' => true,
985 'stroke-opacity' => true,
986 'stroke-width' => true,
987 ),
988 'section' => $allow_class,
989 'span' => array(
990 'class' => true,
991 'id' => true,
992 'title' => true,
993 'style' => true,
994 'aria-hidden' => true,
995 ),
996 'strike' => array(),
997 'strong' => array(),
998 'symbol' => array(
999 'class' => true,
1000 'id' => true,
1001 'viewbox' => true,
1002 ),
1003 'svg' => array(
1004 'class' => true,
1005 'id' => true,
1006 'xmlns' => true,
1007 'viewbox' => true,
1008 'width' => true,
1009 'height' => true,
1010 'style' => true,
1011 'fill' => true,
1012 'aria-label' => true,
1013 'aria-hidden' => true,
1014 ),
1015 'use' => array(
1016 'href' => true,
1017 'xlink:href' => true,
1018 ),
1019 'ul' => $allow_class,
1020 'label' => array(
1021 'for' => true,
1022 'class' => true,
1023 'id' => true,
1024 ),
1025 'button' => array(
1026 'class' => true,
1027 'type' => true,
1028 ),
1029 'legend' => array(
1030 'class' => true,
1031 ),
1032 );
1033 }
1034
1035 /**
1036 * Used when switching the action for a bulk action
1037 *
1038 * @since 2.0
1039 */
1040 public static function remove_get_action() {
1041 if ( empty( $_GET ) ) {
1042 return;
1043 }
1044
1045 $action_name = isset( $_GET['action'] ) ? 'action' : ( isset( $_GET['action2'] ) ? 'action2' : '' );
1046 if ( empty( $action_name ) ) {
1047 return;
1048 }
1049
1050 $new_action = self::get_param( $action_name, '', 'get', 'sanitize_text_field' );
1051 if ( ! empty( $new_action ) ) {
1052 $_SERVER['REQUEST_URI'] = str_replace( '&action=' . $new_action, '', self::get_server_value( 'REQUEST_URI' ) );
1053 }
1054 }
1055
1056 /**
1057 * Check the WP query for a parameter
1058 *
1059 * @since 2.0
1060 * @return string|array
1061 */
1062 public static function get_query_var( $value, $param ) {
1063 if ( $value != '' ) {
1064 return $value;
1065 }
1066
1067 global $wp_query;
1068 if ( isset( $wp_query->query_vars[ $param ] ) ) {
1069 $value = $wp_query->query_vars[ $param ];
1070 }
1071
1072 return $value;
1073 }
1074
1075 /**
1076 * Try to show the SVG if possible. Otherwise, use the font icon.
1077 *
1078 * @since 4.0.02
1079 *
1080 * @param string $class
1081 * @param array $atts
1082 * @return string|null
1083 */
1084 public static function icon_by_class( $class, $atts = array() ) {
1085 $echo = ! isset( $atts['echo'] ) || $atts['echo'];
1086 if ( isset( $atts['echo'] ) ) {
1087 unset( $atts['echo'] );
1088 }
1089
1090 $html_atts = self::array_to_html_params( $atts );
1091
1092 $icon = trim( str_replace( array( 'frm_icon_font', 'frmfont ' ), '', $class ) );
1093
1094 // Replace icons that have been removed or renamed.
1095 $deprecated = array(
1096 'frm_clone_solid_icon' => 'frm_clone_icon',
1097 'frm_keyalt_icon' => 'frm_key_icon',
1098 'frm_keyalt_solid_icon' => 'frm_key_solid_icon',
1099 );
1100 if ( isset( $deprecated[ $icon ] ) ) {
1101 $icon = $deprecated[ $icon ];
1102 $class = str_replace( $icon, $deprecated[ $icon ], $class );
1103 }
1104
1105 if ( $icon === $class ) {
1106 $icon = '<i class="' . esc_attr( $class ) . '"' . $html_atts . '></i>';
1107 } else {
1108 $class = strpos( $icon, ' ' ) === false ? '' : ' ' . $icon;
1109 if ( strpos( $icon, ' ' ) ) {
1110 $icon = explode( ' ', $icon );
1111 $icon = reset( $icon );
1112 }
1113 $icon = '<svg class="frmsvg' . esc_attr( $class ) . '"' . $html_atts . '>
1114 <use xlink:href="#' . esc_attr( $icon ) . '" />
1115 </svg>';
1116 }
1117
1118 if ( $echo ) {
1119 echo self::kses_icon( $icon ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1120 } else {
1121 return $icon;
1122 }
1123 }
1124
1125 /**
1126 * Run kses for icons. It needs to add a few filters first in order to preserve some custom style values.
1127 *
1128 * @since 5.0.13
1129 *
1130 * @param string $icon
1131 * @return string
1132 */
1133 public static function kses_icon( $icon ) {
1134 add_filter( 'safe_style_css', 'FrmAppHelper::allow_vars_in_styles' );
1135 add_filter( 'safecss_filter_attr_allow_css', 'FrmAppHelper::allow_style', 10, 2 );
1136 add_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_icon_tags' );
1137 $icon = self::kses( $icon, 'all' );
1138 remove_filter( 'safe_style_css', 'FrmAppHelper::allow_vars_in_styles' );
1139 remove_filter( 'safecss_filter_attr_allow_css', 'FrmAppHelper::allow_style' );
1140 remove_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_icon_tags' );
1141 return $icon;
1142 }
1143
1144 /**
1145 * @since 5.0.13.1
1146 *
1147 * @param array $allowed_html
1148 * @return array
1149 */
1150 public static function add_allowed_icon_tags( $allowed_html ) {
1151 $allowed_html['svg']['data-open'] = true;
1152 $allowed_html['svg']['title'] = true;
1153 return $allowed_html;
1154 }
1155
1156 /**
1157 * @since 5.0.13
1158 *
1159 * @param array $allowed_attr
1160 * @return array
1161 */
1162 public static function allow_vars_in_styles( $allowed_attr ) {
1163 $allowed_attr[] = '--primary-700';
1164 return $allowed_attr;
1165 }
1166
1167 /**
1168 * @since 5.0.13
1169 *
1170 * @param bool $allow_css
1171 * @param string $css_string
1172 */
1173 public static function allow_style( $allow_css, $css_string ) {
1174 if ( ! $allow_css && 0 === strpos( $css_string, '--primary-700:' ) ) {
1175 $split = explode( ':', $css_string, 2 );
1176 $allow_css = 2 === count( $split ) && self::is_a_valid_color( $split[1] );
1177 }
1178 return $allow_css;
1179 }
1180
1181 /**
1182 * @since 5.0.13
1183 *
1184 * @param string $value
1185 * @return bool
1186 */
1187 private static function is_a_valid_color( $value ) {
1188 $match = 0;
1189 if ( 0 === strpos( $value, 'rgba(' ) ) {
1190 $match = preg_match( '/^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d*(?:\.\d+)?)\)$/', $value );
1191 } elseif ( 0 === strpos( $value, 'rgb(' ) ) {
1192 $match = preg_match( '/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/', $value );
1193 } elseif ( 0 === strpos( $value, '#' ) ) {
1194 $match = preg_match( '/^#([a-f0-9]{6}|[a-f0-9]{3})\b$/', $value );
1195 }
1196 return (bool) $match;
1197 }
1198
1199 /**
1200 * Include svg images.
1201 *
1202 * @since 4.0.02
1203 * @return void
1204 */
1205 public static function include_svg() {
1206 if ( self::$included_svg ) {
1207 return;
1208 }
1209
1210 // Use readfile instead of include_once because of a default security rule in Snuffleupagus.
1211 readfile( self::plugin_path() . '/images/icons.svg' );
1212 self::$included_svg = true;
1213 }
1214
1215 /**
1216 * Convert an associative array to HTML values.
1217 *
1218 * @since 4.0.02
1219 * @since 5.0.13 added $echo parameter.
1220 *
1221 * @param array $atts
1222 * @param bool $echo
1223 * @return string|void
1224 */
1225 public static function array_to_html_params( $atts, $echo = false ) {
1226 $callback = function () use ( $atts ) {
1227 if ( $atts ) {
1228 foreach ( $atts as $key => $value ) {
1229 echo ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
1230 }
1231 }
1232 };
1233 return self::clip( $callback, $echo );
1234 }
1235
1236 /**
1237 * Call an echo function and either echo it or return the result as a string.
1238 *
1239 * @since 5.0.13
1240 *
1241 * @param Closure $echo_function
1242 * @param bool $echo
1243 * @return string|null
1244 */
1245 public static function clip( $echo_function, $echo = false ) {
1246 if ( ! $echo ) {
1247 ob_start();
1248 }
1249
1250 if ( is_callable( $echo_function ) ) {
1251 $echo_function();
1252 }
1253
1254 if ( ! $echo ) {
1255 $return = ob_get_contents();
1256 ob_end_clean();
1257 return $return;
1258 }
1259 }
1260
1261 /**
1262 * @since 3.0
1263 *
1264 * @param array $atts
1265 * @return void
1266 */
1267 public static function get_admin_header( $atts ) {
1268 $has_nav = ! empty( $atts['form'] ) && empty( $atts['is_template'] );
1269 if ( ! isset( $atts['close'] ) || empty( $atts['close'] ) ) {
1270 $atts['close'] = admin_url( 'admin.php?page=formidable' );
1271 }
1272 if ( ! isset( $atts['import_link'] ) ) {
1273 $atts['import_link'] = false;
1274 }
1275
1276 include self::plugin_path() . '/classes/views/shared/admin-header.php';
1277 }
1278
1279 /**
1280 * @since 6.0
1281 *
1282 * @param string $type
1283 * @return void
1284 */
1285 public static function import_link( $type = 'secondary' ) {
1286 ?>
1287 <a href="<?php echo esc_url( admin_url( 'admin.php?page=formidable-import' ) ); ?>" class="button frm-button-<?php echo esc_attr( $type ); ?> frm_animate_bg">
1288 <?php esc_html_e( 'Import', 'formidable' ); ?>
1289 </a>
1290 <?php
1291 }
1292
1293 /**
1294 * Print applicable admin banner.
1295 *
1296 * @since 5.4.2
1297 *
1298 * @param bool $should_show_lite_upgrade
1299 * @return void
1300 */
1301 public static function print_admin_banner( $should_show_lite_upgrade ) {
1302 if ( ! current_user_can( 'administrator' ) ) {
1303 FrmInbox::maybe_show_banner();
1304 return;
1305 }
1306
1307 if ( self::maybe_show_license_warning() || FrmInbox::maybe_show_banner() || ! $should_show_lite_upgrade || self::pro_is_installed() ) {
1308 // Print license warning or inbox banner and exit if either prints.
1309 // And exit before printing the upgrade bar if it shouldn't be shown.
1310 return;
1311 }
1312 ?>
1313 <div class="frm-upgrade-bar">
1314 <span>You're using Formidable Forms Lite. To unlock more features consider</span>
1315 <a href="<?php echo esc_url( self::admin_upgrade_link( 'top-bar' ) ); ?>" target="_blank" rel="noopener">upgrading to Pro</a>.
1316 </div>
1317 <?php
1318 }
1319
1320 /**
1321 * @since 5.4.2
1322 *
1323 * @return bool True if a banner is available and shown.
1324 */
1325 private static function maybe_show_license_warning() {
1326 return is_callable( 'FrmProAddonsController::admin_banner' ) && FrmProAddonsController::admin_banner();
1327 }
1328
1329 /**
1330 * Render a button for a new item (Form, Application, etc).
1331 *
1332 * @since 3.0
1333 * @param array $atts {
1334 * Details about the button.
1335 *
1336 * @type array $link_hook Custom link hook, calls do_action and exits early.
1337 * @type string $new_link Href value, default #.
1338 * @type string $class Custom class names, space separated.
1339 * @type string $button_text Button text. Default "Add New".
1340 * }
1341 * @return void
1342 */
1343 public static function add_new_item_link( $atts ) {
1344 if ( isset( $atts['link_hook'] ) ) {
1345 do_action( $atts['link_hook']['hook'], $atts['link_hook']['param'] );
1346 return;
1347 }
1348
1349 if ( empty( $atts['new_link'] ) && empty( $atts['create_form'] ) && empty( $atts['class'] ) ) {
1350 // Do not render a button if none of these attributes are set.
1351 return;
1352 }
1353
1354 $href = ! empty( $atts['new_link'] ) ? esc_url( $atts['new_link'] ) : '#';
1355 $class = 'button button-primary frm-button-primary';
1356
1357 if ( ! empty( $atts['class'] ) ) {
1358 $class .= ' ' . $atts['class'];
1359 }
1360
1361 $button_text = ! empty( $atts['button_text'] ) ? $atts['button_text'] : __( 'Add New', 'formidable' );
1362
1363 require self::plugin_path() . '/classes/views/shared/add-button.php';
1364 }
1365
1366 /**
1367 * @since 3.06
1368 */
1369 public static function show_search_box( $atts ) {
1370 $defaults = array(
1371 'placeholder' => '',
1372 'tosearch' => '',
1373 'text' => __( 'Search', 'formidable' ),
1374 'input_id' => '',
1375 'value' => false,
1376 );
1377 $atts = array_merge( $defaults, $atts );
1378
1379 if ( $atts['input_id'] === 'template' && empty( $atts['tosearch'] ) ) {
1380 $atts['tosearch'] = 'frm-card';
1381 }
1382
1383 $class = 'frm-search-input';
1384 if ( ! empty( $atts['tosearch'] ) ) {
1385 $class .= ' frm-auto-search';
1386 }
1387
1388 $input_id = $atts['input_id'] . '-search-input';
1389
1390 $input_atts = array(
1391 'type' => 'search',
1392 'id' => $input_id,
1393 'name' => 's',
1394 'placeholder' => $atts['placeholder'],
1395 'class' => $class,
1396 'data-tosearch' => $atts['tosearch'],
1397 );
1398
1399 if ( is_string( $atts['value'] ) ) {
1400 $input_atts['value'] = $atts['value'];
1401 } elseif ( isset( $_REQUEST['s'] ) ) {
1402 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1403 $input_atts['value'] = wp_unslash( $_REQUEST['s'] );
1404 }
1405
1406 if ( ! empty( $atts['tosearch'] ) ) {
1407 $input_atts['autocomplete'] = 'off';
1408 }
1409 ?>
1410 <p class="frm-search">
1411 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>">
1412 <?php echo esc_html( $atts['text'] ); ?>:
1413 </label>
1414 <span class="frmfont frm_search_icon"></span>
1415 <input <?php self::array_to_html_params( $input_atts, true ); ?> />
1416 <?php
1417 if ( empty( $atts['tosearch'] ) ) {
1418 submit_button( $atts['text'], 'button-secondary', '', false, array( 'id' => 'search-submit' ) );
1419 }
1420 ?>
1421 </p>
1422 <?php
1423 }
1424
1425 /**
1426 * @param string $type
1427 * @return void
1428 */
1429 public static function trigger_hook_load( $type, $object = null ) {
1430 // Only load the form hooks once.
1431 $hooks_loaded = apply_filters( 'frm_' . $type . '_hooks_loaded', false, $object );
1432 if ( ! $hooks_loaded ) {
1433 do_action( 'frm_load_' . $type . '_hooks' );
1434 }
1435 }
1436
1437 /**
1438 * Save all front-end js scripts into a single file.
1439 * And save an additional single file of all front-end Stripe JS scripts.
1440 *
1441 * @since 3.0
1442 *
1443 * @return void
1444 */
1445 public static function save_combined_js() {
1446 $file_atts = apply_filters(
1447 'frm_js_location',
1448 array(
1449 'file_name' => 'frm.min.js',
1450 'new_file_path' => self::plugin_path() . '/js',
1451 )
1452 );
1453 $new_file = new FrmCreateFile( $file_atts );
1454
1455 $files = array(
1456 self::plugin_path() . '/js/formidable.min.js',
1457 );
1458 /**
1459 * @param array $files
1460 */
1461 $files = apply_filters( 'frm_combined_js_files', $files );
1462 $new_file->combine_files( $files );
1463
1464 // Create the minified Stripe Script.
1465 $file_atts = apply_filters(
1466 'frm_stripe_js_location',
1467 array(
1468 'file_name' => 'frmstrp.min.js',
1469 'new_file_path' => self::plugin_path() . '/js',
1470 )
1471 );
1472 $new_file = new FrmCreateFile( $file_atts );
1473 $files = array(
1474 FrmStrpLiteAppHelper::plugin_path() . 'js/frmstrp.min.js',
1475 );
1476
1477 /**
1478 * @since 6.5
1479 *
1480 * @param array $files
1481 */
1482 $files = apply_filters( 'frm_stripe_combined_js_files', $files );
1483 $new_file->combine_files( $files );
1484 }
1485
1486 /**
1487 * Check a value from a shortcode to see if true or false.
1488 * True when value is 1, true, 'true', 'yes'
1489 *
1490 * @since 1.07.10
1491 *
1492 * @param string $value The value to compare.
1493 *
1494 * @return boolean True or False
1495 */
1496 public static function is_true( $value ) {
1497 return ( true === $value || 1 == $value || 'true' == $value || 'yes' == $value );
1498 }
1499
1500 /**
1501 * Gets all post from a specific post type.
1502 * This gets the entire WP_Post object so it can require a lot of memory. When only id and title are needed, consider using FrmAppHelper::get_post_ids_and_titles instead.
1503 *
1504 * @since 4.10.01 Add `$post_type` argument.
1505 *
1506 * @param string $post_type Post type to query. Default is `page`.
1507 * @return WP_Post[]
1508 */
1509 public static function get_pages( $post_type = 'page' ) {
1510 $query = array(
1511 'post_type' => $post_type,
1512 'post_status' => array( 'publish', 'private' ),
1513 'numberposts' => - 1,
1514 'orderby' => 'title',
1515 'order' => 'ASC',
1516 );
1517
1518 return get_posts( $query );
1519 }
1520
1521 /**
1522 * Gets post ids and titles for a specific post type.
1523 *
1524 * @since 5.0.09
1525 *
1526 * @param string $post_type Post type to query. Default is `page`.
1527 * @return array
1528 */
1529 public static function get_post_ids_and_titles( $post_type = 'page' ) {
1530 return FrmDb::get_results(
1531 'posts',
1532 array(
1533 'post_type' => $post_type,
1534 'post_status' => array( 'publish', 'private' ),
1535 ),
1536 'ID, post_title',
1537 array(
1538 'order_by' => 'post_title ASC',
1539 )
1540 );
1541 }
1542
1543 /**
1544 * Renders an autocomplete page selection or a regular dropdown depending on
1545 * the total page count
1546 *
1547 * @since 4.03.06
1548 * @since 4.10.01 Added `post_type` and `autocomplete_placeholder` to the arguments array.
1549 *
1550 * @param array $args Selection arguments.
1551 */
1552 public static function maybe_autocomplete_pages_options( $args ) {
1553 $args = self::preformat_selection_args( $args );
1554
1555 $pages_count = wp_count_posts( $args['post_type'] );
1556
1557 if ( $pages_count->publish <= 50 ) {
1558 self::wp_pages_dropdown( $args );
1559 return;
1560 }
1561
1562 wp_enqueue_script( 'jquery-ui-autocomplete' );
1563
1564 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1565 $title = '';
1566
1567 if ( $selected ) {
1568 $title = get_the_title( $selected );
1569 }
1570
1571 ?>
1572 <input type="text" class="frm-page-search"
1573 data-post-type="<?php echo esc_attr( $args['post_type'] ); ?>"
1574 placeholder="<?php echo esc_attr( $args['autocomplete_placeholder'] ); ?>"
1575 value="<?php echo esc_attr( $title ); ?>" />
1576 <input type="hidden" name="<?php echo esc_attr( $args['field_name'] ); ?>"
1577 class="frm_autocomplete_value_input"
1578 value="<?php echo esc_attr( $selected ); ?>" />
1579 <?php
1580 }
1581
1582 /**
1583 * @param array $args
1584 * @param string $page_id Deprecated.
1585 * @param boolean $truncate Deprecated.
1586 */
1587 public static function wp_pages_dropdown( $args = array(), $page_id = '', $truncate = false ) {
1588 self::prep_page_dropdown_params( $page_id, $truncate, $args );
1589
1590 $pages = self::get_post_ids_and_titles( $args['post_type'] );
1591 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1592
1593 ?>
1594 <select name="<?php echo esc_attr( $args['field_name'] ); ?>" id="<?php echo esc_attr( $args['field_name'] ); ?>" class="frm-pages-dropdown">
1595 <option value=""><?php echo esc_html( $args['placeholder'] ); ?></option>
1596 <?php foreach ( $pages as $page ) { ?>
1597 <option value="<?php echo esc_attr( $page->ID ); ?>" <?php selected( $selected, $page->ID ); ?>>
1598 <?php echo esc_html( $args['truncate'] ? self::truncate( $page->post_title, $args['truncate'] ) : $page->post_title ); ?>
1599 </option>
1600 <?php } ?>
1601 </select>
1602 <?php
1603 }
1604
1605 /**
1606 * Fill in missing parameters passed to wp_pages_dropdown().
1607 * This is for reverse compatibility with switching 3 params to 1.
1608 *
1609 * @since 4.03.06
1610 */
1611 private static function prep_page_dropdown_params( $page_id, $truncate, &$args ) {
1612 if ( ! is_array( $args ) ) {
1613 $args = array(
1614 'field_name' => $args,
1615 'page_id' => $page_id,
1616 'truncate' => $truncate,
1617 );
1618 }
1619
1620 $args = self::preformat_selection_args( $args );
1621 }
1622
1623 /**
1624 * Filter to format args for page dropdown or autocomplete
1625 *
1626 * @since 4.03.06
1627 * @since 4.10.01 Added `post_type` and `autocomplete_placeholder` to the arguments array.
1628 */
1629 private static function preformat_selection_args( $args ) {
1630 $defaults = array(
1631 'truncate' => false,
1632 'placeholder' => ' ',
1633 'field_name' => '',
1634 'page_id' => '',
1635 'post_type' => 'page',
1636 'autocomplete_placeholder' => __( 'Select a Page', 'formidable' ),
1637 );
1638
1639 return array_merge( $defaults, $args );
1640 }
1641
1642 public static function post_edit_link( $post_id ) {
1643 $post = get_post( $post_id );
1644 if ( $post ) {
1645 $post_url = admin_url( 'post.php?post=' . $post_id . '&action=edit' );
1646
1647 return '<a href="' . esc_url( $post_url ) . '">' . self::truncate( $post->post_title, 50 ) . '</a>';
1648 }
1649
1650 return '';
1651 }
1652
1653 /**
1654 * Hide the WordPress menus on some pages.
1655 *
1656 * @since 4.0
1657 *
1658 * @return bool
1659 */
1660 public static function is_full_screen() {
1661 return self::is_form_builder_page() ||
1662 self::is_style_editor_page() ||
1663 self::is_full_screen_view_builder_page();
1664 }
1665
1666 /**
1667 * Check if user is on the style editor or its alternative URL.
1668 * The first URL is a submenu "Styles" in the Formidable menu /wp-admin/admin.php?page=formidable-styles.
1669 * The alternative URL is linked as a submenu "Forms" item of the Appearance menu /wp-admin/themes.php?page=formidable-styles2.
1670 *
1671 * @since 5.5.3
1672 * @since 6.0 Added the $view parameter. Previously there was only a 'edit' view.
1673 *
1674 * @param string $view Supports 'edit', 'list', and ''. If '', both 'edit' and 'list' will match.
1675 * @return bool
1676 */
1677 public static function is_style_editor_page( $view = '' ) {
1678 if ( ! self::is_admin_page( 'formidable-styles' ) && ! self::is_admin_page( 'formidable-styles2' ) ) {
1679 return false;
1680 }
1681
1682 if ( ! in_array( $view, array( 'list', 'edit' ), true ) ) {
1683 return true;
1684 }
1685
1686 $action = self::simple_get( 'frm_action' );
1687 $is_edit_mode = 'edit' === $action || ( ! $action && ! self::simple_get( 'id' ) && ! self::simple_get( 'form' ) );
1688
1689 if ( ! $is_edit_mode && class_exists( 'FrmProStylesController' ) && in_array( $action, array( 'new_style', 'duplicate' ), true ) ) {
1690 $is_edit_mode = true;
1691 }
1692
1693 $checking_for_edit_mode = 'edit' === $view;
1694
1695 return $is_edit_mode === $checking_for_edit_mode;
1696 }
1697
1698 /**
1699 * @since 5.5.3
1700 *
1701 * @return bool
1702 */
1703 private static function is_full_screen_view_builder_page() {
1704 return self::is_admin_page( 'formidable-views-editor' );
1705 }
1706
1707 /**
1708 * @since 4.0
1709 * @deprecated 5.5.3
1710 */
1711 public static function maybe_full_screen_link( $link ) {
1712 _deprecated_function( __METHOD__, '5.5.3' );
1713 return $link;
1714 }
1715
1716 /**
1717 * @param string $field_name
1718 * @param string|array $capability
1719 * @param string $multiple 'single' and 'multiple'.
1720 */
1721 public static function wp_roles_dropdown( $field_name, $capability, $multiple = 'single' ) {
1722 ?>
1723 <select name="<?php echo esc_attr( $field_name ); ?>" id="<?php echo esc_attr( $field_name ); ?>"
1724 <?php echo ( 'multiple' === $multiple ) ? 'multiple="multiple"' : ''; ?>
1725 class="frm_multiselect">
1726 <?php self::roles_options( $capability ); ?>
1727 </select>
1728 <?php
1729 }
1730
1731 /**
1732 * @since 4.07
1733 * @param array|string $selected
1734 * @param string $current
1735 */
1736 private static function selected( $selected, $current ) {
1737 if ( is_callable( 'FrmProAppHelper::selected' ) ) {
1738 FrmProAppHelper::selected( $selected, $current );
1739 } else {
1740 selected( in_array( $current, (array) $selected, true ) );
1741 }
1742 }
1743
1744 /**
1745 * @param string|array $capability
1746 */
1747 public static function roles_options( $capability ) {
1748 global $frm_vars;
1749 if ( isset( $frm_vars['editable_roles'] ) ) {
1750 $editable_roles = $frm_vars['editable_roles'];
1751 } else {
1752 $editable_roles = get_editable_roles();
1753 $frm_vars['editable_roles'] = $editable_roles;
1754 }
1755
1756 foreach ( $editable_roles as $role => $details ) {
1757 $name = translate_user_role( $details['name'] );
1758 ?>
1759 <option value="<?php echo esc_attr( $role ); ?>" <?php self::selected( $capability, $role ); ?>><?php echo esc_attr( $name ); ?> </option>
1760 <?php
1761 unset( $role, $details );
1762 }
1763 }
1764
1765 /**
1766 * Gets the list of capabilities.
1767 *
1768 * @since 5.0 Parameter `$type` supports `pro_only` value.
1769 *
1770 * @param string $type Supports `auto`, `pro`, or `pro_only`.
1771 * @return array
1772 */
1773 public static function frm_capabilities( $type = 'auto' ) {
1774 if ( ! self::pro_is_installed() && ! in_array( $type, array( 'pro', 'pro_only' ), true ) ) {
1775 return self::get_lite_capabilities();
1776 }
1777
1778 $pro_cap = array(
1779 'frm_create_entries' => __( 'Add Entries from Admin Area', 'formidable' ),
1780 'frm_edit_entries' => __( 'Edit Entries from Admin Area', 'formidable' ),
1781 'frm_view_reports' => __( 'View Reports', 'formidable' ),
1782 );
1783 /**
1784 * @since 5.3.1
1785 *
1786 * @param array<string,string> $pro_cap
1787 */
1788 $pro_cap = apply_filters( 'frm_pro_capabilities', $pro_cap );
1789
1790 if ( ! array_key_exists( 'frm_edit_displays', $pro_cap ) && is_callable( 'FrmProAppHelper::views_is_installed' ) && FrmProAppHelper::views_is_installed() ) {
1791 // For backward compatibility, add the Add/Edit Views permission if Pro is not up to date.
1792 // This was added in 6.5.4. Remove this in the future.
1793 $pro_cap['frm_edit_displays'] = __( 'Add/Edit Views', 'formidable' );
1794 }
1795
1796 if ( 'pro_only' === $type ) {
1797 return $pro_cap;
1798 }
1799
1800 return self::get_lite_capabilities() + $pro_cap;
1801 }
1802
1803 /**
1804 * Get the list of lite plugin capabilities.
1805 *
1806 * @since 5.3.1
1807 *
1808 * @return array<string,string>
1809 */
1810 private static function get_lite_capabilities() {
1811 return array(
1812 'frm_view_forms' => __( 'View Forms List', 'formidable' ),
1813 'frm_edit_forms' => __( 'Add and Edit Forms', 'formidable' ),
1814 'frm_delete_forms' => __( 'Delete Forms', 'formidable' ),
1815 'frm_change_settings' => __( 'Access this Settings Page', 'formidable' ),
1816 'frm_view_entries' => __( 'View Entries from Admin Area', 'formidable' ),
1817 'frm_delete_entries' => __( 'Delete Entries from Admin Area', 'formidable' ),
1818 );
1819 }
1820
1821 /**
1822 * Call the WordPress current_user_can but also validate empty strings as true for any logged in user
1823 *
1824 * @since 4.06.03
1825 *
1826 * @param string $role
1827 *
1828 * @return bool
1829 */
1830 public static function current_user_can( $role ) {
1831 if ( $role === '-1' ) {
1832 return false;
1833 }
1834
1835 if ( $role === 'loggedout' ) {
1836 return ! is_user_logged_in();
1837 }
1838
1839 if ( $role === 'loggedin' || ! $role ) {
1840 return is_user_logged_in();
1841 }
1842
1843 if ( $role == 1 ) {
1844 $role = 'administrator';
1845 }
1846
1847 if ( ! is_user_logged_in() ) {
1848 return false;
1849 }
1850
1851 return current_user_can( $role );
1852 }
1853
1854 /**
1855 * @param string|array $needed_role
1856 * @return bool
1857 */
1858 public static function user_has_permission( $needed_role ) {
1859 if ( is_array( $needed_role ) ) {
1860 foreach ( $needed_role as $role ) {
1861 if ( self::current_user_can( $role ) ) {
1862 return true;
1863 }
1864 }
1865
1866 return false;
1867 }
1868
1869 $can = self::current_user_can( $needed_role );
1870
1871 if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ) ) ) {
1872 return $can;
1873 }
1874
1875 $roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
1876 foreach ( $roles as $role ) {
1877 if ( current_user_can( $role ) ) {
1878 return true;
1879 }
1880 if ( $role == $needed_role ) {
1881 break;
1882 }
1883 }
1884
1885 return false;
1886 }
1887
1888 /**
1889 * Make sure administrators can see Formidable menu
1890 *
1891 * @since 2.0
1892 */
1893 public static function maybe_add_permissions() {
1894 self::force_capability( 'frm_view_entries' );
1895
1896 if ( ! current_user_can( 'administrator' ) || current_user_can( 'frm_view_forms' ) ) {
1897 return;
1898 }
1899
1900 $user_id = get_current_user_id();
1901 $user = new WP_User( $user_id );
1902 $frm_roles = self::frm_capabilities();
1903 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1904 $user->add_cap( $frm_role );
1905 unset( $frm_role, $frm_role_description );
1906 }
1907 }
1908
1909 /**
1910 * Make sure admins have permission to see the menu items
1911 *
1912 * @since 2.0.6
1913 *
1914 * @param string $cap
1915 * @return void
1916 */
1917 public static function force_capability( $cap = 'frm_change_settings' ) {
1918 if ( current_user_can( 'administrator' ) && ! current_user_can( $cap ) ) {
1919 $role = get_role( 'administrator' );
1920 $frm_roles = self::frm_capabilities();
1921 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1922 $role->add_cap( $frm_role );
1923 }
1924 }
1925 }
1926
1927 /**
1928 * Check if the user has permision for action.
1929 * Return permission message and stop the action if no permission
1930 *
1931 * @since 2.0
1932 *
1933 * @param string $permission
1934 */
1935 public static function permission_check( $permission, $show_message = 'show' ) {
1936 $permission_error = self::permission_nonce_error( $permission );
1937 if ( $permission_error !== false ) {
1938 if ( 'hide' == $show_message ) {
1939 $permission_error = '';
1940 }
1941 wp_die( esc_html( $permission_error ) );
1942 }
1943 }
1944
1945 /**
1946 * Check user permission and nonce
1947 *
1948 * @since 2.0
1949 *
1950 * @param string $permission
1951 *
1952 * @return false|string The permission message or false if allowed
1953 */
1954 public static function permission_nonce_error( $permission, $nonce_name = '', $nonce = '' ) {
1955 if ( ! empty( $permission ) && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
1956 $frm_settings = self::get_settings();
1957
1958 return $frm_settings->admin_permission;
1959 }
1960
1961 $error = false;
1962 if ( empty( $nonce_name ) ) {
1963 return $error;
1964 }
1965
1966 $nonce_value = ( $_REQUEST && isset( $_REQUEST[ $nonce_name ] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_name ] ) ) : '';
1967 if ( $_REQUEST && ( ! isset( $_REQUEST[ $nonce_name ] ) || ! wp_verify_nonce( $nonce_value, $nonce ) ) ) {
1968 $frm_settings = self::get_settings();
1969 $error = $frm_settings->admin_permission;
1970 }
1971
1972 return $error;
1973 }
1974
1975 public static function checked( $values, $current ) {
1976 if ( self::check_selected( $values, $current ) ) {
1977 echo ' checked="checked"';
1978 }
1979 }
1980
1981 public static function check_selected( $values, $current ) {
1982 $values = self::recursive_function_map( $values, 'trim' );
1983 $values = self::recursive_function_map( $values, 'htmlspecialchars_decode' );
1984 $current = htmlspecialchars_decode( trim( $current ) );
1985
1986 return ( is_array( $values ) && in_array( $current, $values ) ) || ( ! is_array( $values ) && $values == $current );
1987 }
1988
1989 public static function recursive_function_map( $value, $function ) {
1990 if ( is_array( $value ) ) {
1991 $original_function = $function;
1992 if ( count( $value ) ) {
1993 $function = explode( ', ', FrmDb::prepare_array_values( $value, $function ) );
1994 } else {
1995 $function = array( $function );
1996 }
1997 if ( ! self::is_assoc( $value ) ) {
1998 $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
1999 } else {
2000 foreach ( $value as $k => $v ) {
2001 if ( ! is_array( $v ) ) {
2002 $value[ $k ] = call_user_func( $original_function, $v );
2003 }
2004 }
2005 }
2006 } else {
2007 $value = self::maybe_update_value_if_null( $value, $function );
2008 $value = call_user_func( $function, $value );
2009 }
2010
2011 return $value;
2012 }
2013
2014 /**
2015 * Updates value to empty string if it is null and being passed to a string function.
2016 *
2017 * @since 6.8.4
2018 * @param mixed $value
2019 * @param string $function
2020 * @return mixed
2021 */
2022 private static function maybe_update_value_if_null( $value, $function ) {
2023 if ( null === $value && in_array( $function, array( 'trim', 'strlen' ), true ) ) {
2024 $value = '';
2025 }
2026
2027 return $value;
2028 }
2029
2030 public static function is_assoc( $array ) {
2031 return (bool) count( array_filter( array_keys( $array ), 'is_string' ) );
2032 }
2033
2034 /**
2035 * Flatten a multi-dimensional array
2036 */
2037 public static function array_flatten( $array, $keys = 'keep' ) {
2038 $return = array();
2039 foreach ( $array as $key => $value ) {
2040 if ( is_array( $value ) ) {
2041 $return = array_merge( $return, self::array_flatten( $value, $keys ) );
2042 } elseif ( $keys === 'keep' ) {
2043 $return[ $key ] = $value;
2044 } else {
2045 $return[] = $value;
2046 }
2047 }
2048
2049 return $return;
2050 }
2051
2052 /**
2053 * @param string $text
2054 * @param bool $is_rich_text
2055 * @return string
2056 */
2057 public static function esc_textarea( $text, $is_rich_text = false ) {
2058 $safe_text = str_replace( '&quot;', '"', $text );
2059 if ( ! $is_rich_text ) {
2060 $safe_text = htmlspecialchars( $safe_text, ENT_NOQUOTES );
2061 }
2062 $safe_text = str_replace( '&amp; ', '& ', $safe_text );
2063
2064 /**
2065 * @param string $safe_text
2066 * @param string $text
2067 */
2068 return (string) apply_filters( 'esc_textarea', $safe_text, $text );
2069 }
2070
2071 /**
2072 * Add auto paragraphs to text areas
2073 *
2074 * @since 2.0
2075 */
2076 public static function use_wpautop( $content ) {
2077 if ( apply_filters( 'frm_use_wpautop', true ) && ! is_array( $content ) ) {
2078 $content = wpautop( str_replace( '<br>', '<br />', $content ) );
2079 }
2080
2081 return $content;
2082 }
2083
2084 public static function replace_quotes( $val ) {
2085 // Replace double quotes.
2086 $val = str_replace( array( '&#8220;', '&#8221;', '&#8243;' ), '"', $val );
2087
2088 // Replace single quotes.
2089 $val = str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
2090
2091 return $val;
2092 }
2093
2094 /**
2095 * @param string $handle
2096 */
2097 public static function script_version( $handle, $default = 0 ) {
2098 global $wp_scripts;
2099 if ( ! $wp_scripts ) {
2100 return $default;
2101 }
2102
2103 $ver = $default;
2104 if ( ! isset( $wp_scripts->registered[ $handle ] ) ) {
2105 return $ver;
2106 }
2107
2108 $query = $wp_scripts->registered[ $handle ];
2109 if ( is_object( $query ) && ! empty( $query->ver ) ) {
2110 $ver = $query->ver;
2111 }
2112
2113 return $ver;
2114 }
2115
2116 /**
2117 * @since 5.0.13 added $echo param.
2118 *
2119 * @param string $url
2120 * @param bool $echo
2121 * @return string|null
2122 */
2123 public static function js_redirect( $url, $echo = false ) {
2124 $callback = function () use ( $url ) {
2125 echo '<script type="text/javascript">window.location="' . esc_url_raw( $url ) . '"</script>';
2126 };
2127 return self::clip( $callback, $echo );
2128 }
2129
2130 public static function get_user_id_param( $user_id ) {
2131 if ( ! $user_id || is_numeric( $user_id ) ) {
2132 return $user_id;
2133 }
2134
2135 $user_id = sanitize_text_field( $user_id );
2136 if ( $user_id === 'current' ) {
2137 $user_id = get_current_user_id();
2138 } else {
2139 if ( is_email( $user_id ) ) {
2140 $user = get_user_by( 'email', $user_id );
2141 } else {
2142 $user = get_user_by( 'login', $user_id );
2143 }
2144
2145 if ( $user ) {
2146 $user_id = $user->ID;
2147 }
2148 unset( $user );
2149 }
2150
2151 return $user_id;
2152 }
2153
2154 /**
2155 * @param string $filename
2156 * @param array $atts
2157 * @return string|false
2158 */
2159 public static function get_file_contents( $filename, $atts = array() ) {
2160 if ( ! is_file( $filename ) ) {
2161 return false;
2162 }
2163
2164 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
2165 ob_start();
2166 include $filename;
2167 $contents = ob_get_contents();
2168 ob_end_clean();
2169
2170 return $contents;
2171 }
2172
2173 /**
2174 * @param string $name
2175 * @param string $table_name
2176 * @param string $column
2177 * @param int $id
2178 * @param int $num_chars
2179 */
2180 public static function get_unique_key( $name, $table_name, $column, $id = 0, $num_chars = 5 ) {
2181 $key = '';
2182 if ( $name ) {
2183 $key = sanitize_key( $name );
2184 $key = self::maybe_clear_long_key( $key, $column );
2185 }
2186
2187 if ( ! $key ) {
2188 $key = self::generate_new_key( $num_chars );
2189 }
2190
2191 $key = self::prevent_numeric_and_reserved_keys( $key );
2192
2193 $similar_keys = FrmDb::get_col(
2194 $table_name,
2195 array(
2196 $column . ' like%' => $key,
2197 'ID !' => $id,
2198 ),
2199 $column
2200 );
2201
2202 // Create a unique field id if it has already been used.
2203 if ( in_array( $key, $similar_keys, true ) ) {
2204 $key = self::maybe_truncate_key_before_appending( $column, $key );
2205
2206 /**
2207 * Allow for a custom separator between the attempted key and the generated suffix.
2208 *
2209 * @since 5.2.03
2210 *
2211 * @param string $separator. Default empty.
2212 * @param string $key the key without the added suffix.
2213 */
2214 $separator = apply_filters( 'frm_unique_' . $column . '_separator', '', $key );
2215
2216 $suffix = 2;
2217 do {
2218 $key_check = $key . $separator . $suffix;
2219 ++$suffix;
2220 } while ( in_array( $key_check, $similar_keys, true ) );
2221
2222 $key = $key_check;
2223 }//end if
2224
2225 return $key;
2226 }
2227
2228 /**
2229 * Avoid trying to append to a really long key,
2230 * The database limit is 100 for form and field keys so we want to avoid getting too close.
2231 *
2232 * @param string $column
2233 * @param string $key
2234 * @return string
2235 */
2236 private static function maybe_truncate_key_before_appending( $column, $key ) {
2237 if ( in_array( $column, array( 'form_key', 'field_key' ), true ) ) {
2238 $max_key_length_before_truncating = 60;
2239 if ( strlen( $key ) > $max_key_length_before_truncating ) {
2240 $key = substr( $key, 0, $max_key_length_before_truncating );
2241 if ( is_numeric( $key ) ) {
2242 $key .= 'a';
2243 }
2244 }
2245 }
2246 return $key;
2247 }
2248
2249 /**
2250 * Possibly reset a key to avoid conflicts with column size limits.
2251 *
2252 * @param string $key
2253 * @param string $column
2254 * @return string either the original key value, or an empty string if the key was too long.
2255 */
2256 private static function maybe_clear_long_key( $key, $column ) {
2257 if ( 'field_key' === $column && strlen( $key ) >= 70 ) {
2258 $key = '';
2259 }
2260 return $key;
2261 }
2262
2263 /**
2264 * @param int $num_chars
2265 * @return string
2266 */
2267 private static function generate_new_key( $num_chars ) {
2268 $max_slug_value = pow( 36, $num_chars );
2269
2270 // We want to have at least 2 characters in the slug.
2271 $min_slug_value = 37;
2272 return base_convert( rand( $min_slug_value, $max_slug_value ), 10, 36 );
2273 }
2274
2275 /**
2276 * @param string $key
2277 * @return string
2278 */
2279 private static function prevent_numeric_and_reserved_keys( $key ) {
2280 if ( is_numeric( $key ) ) {
2281 $key .= 'a';
2282 } else {
2283 $not_allowed = array(
2284 'id',
2285 'key',
2286 'created-at',
2287 'detaillink',
2288 'editlink',
2289 'siteurl',
2290 'evenodd',
2291 );
2292 if ( in_array( $key, $not_allowed, true ) ) {
2293 $key .= 'a';
2294 }
2295 }
2296 return $key;
2297 }
2298
2299 /**
2300 * Editing a Form or Entry
2301 *
2302 * @param object $record
2303 * @param string $table
2304 * @param array|string $fields
2305 * @param bool $default
2306 * @param array $post_values
2307 * @param array $args
2308 *
2309 * @return bool|array
2310 */
2311 public static function setup_edit_vars( $record, $table, $fields = '', $default = false, $post_values = array(), $args = array() ) {
2312 if ( ! $record ) {
2313 return false;
2314 }
2315
2316 if ( empty( $post_values ) ) {
2317 $post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
2318 }
2319
2320 $values = array(
2321 'id' => $record->id,
2322 'fields' => array(),
2323 );
2324
2325 foreach ( array( 'name', 'description' ) as $var ) {
2326 $default_val = isset( $record->{$var} ) ? $record->{$var} : '';
2327 $values[ $var ] = self::get_param( $var, $default_val, 'get', 'wp_kses_post' );
2328 unset( $var, $default_val );
2329 }
2330
2331 $values['description'] = self::use_wpautop( $values['description'] );
2332
2333 self::fill_form_opts( $record, $table, $post_values, $values );
2334
2335 self::prepare_field_arrays( $fields, $record, $values, array_merge( $args, compact( 'default', 'post_values' ) ) );
2336
2337 if ( $table === 'entries' ) {
2338 $values = FrmEntriesHelper::setup_edit_vars( $values, $record );
2339 } elseif ( $table === 'forms' ) {
2340 $values = FrmFormsHelper::setup_edit_vars( $values, $record, $post_values );
2341 }
2342
2343 return $values;
2344 }
2345
2346 private static function prepare_field_arrays( $fields, $record, array &$values, $args ) {
2347 if ( ! empty( $fields ) ) {
2348 foreach ( (array) $fields as $field ) {
2349 if ( ! self::is_admin_page() ) {
2350 // Don't prep default values on the form settings page.
2351 $field->default_value = apply_filters( 'frm_get_default_value', $field->default_value, $field, true );
2352 }
2353 $args['parent_form_id'] = isset( $args['parent_form_id'] ) ? $args['parent_form_id'] : $field->form_id;
2354 self::fill_field_defaults( $field, $record, $values, $args );
2355 }
2356 }
2357 }
2358
2359 private static function fill_field_defaults( $field, $record, array &$values, $args ) {
2360 $post_values = $args['post_values'];
2361
2362 if ( $args['default'] ) {
2363 $meta_value = $field->default_value;
2364 } elseif ( $record->post_id && self::pro_is_installed() && isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] ) {
2365 if ( ! isset( $field->field_options['custom_field'] ) ) {
2366 $field->field_options['custom_field'] = '';
2367 }
2368 $meta_value = FrmProEntryMetaHelper::get_post_value(
2369 $record->post_id,
2370 $field->field_options['post_field'],
2371 $field->field_options['custom_field'],
2372 array(
2373 'truncate' => false,
2374 'type' => $field->type,
2375 'form_id' => $field->form_id,
2376 'field' => $field,
2377 )
2378 );
2379 } else {
2380 $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2381 }//end if
2382
2383 $field_type = isset( $post_values['field_options'][ 'type_' . $field->id ] ) ? $post_values['field_options'][ 'type_' . $field->id ] : $field->type;
2384 if ( isset( $post_values['item_meta'][ $field->id ] ) ) {
2385 $new_value = $post_values['item_meta'][ $field->id ];
2386 self::unserialize_or_decode( $new_value );
2387 } else {
2388 $new_value = $meta_value;
2389 }
2390
2391 $field_array = self::start_field_array( $field );
2392 $field_array['value'] = $new_value;
2393 $field_array['type'] = apply_filters( 'frm_field_type', $field_type, $field, $new_value );
2394 $field_array['parent_form_id'] = $args['parent_form_id'];
2395
2396 $args['field_type'] = $field_type;
2397
2398 FrmFieldsHelper::prepare_edit_front_field( $field_array, $field, $values['id'], $args );
2399
2400 if ( ! isset( $field_array['unique'] ) || ! $field_array['unique'] ) {
2401 $field_array['unique_msg'] = '';
2402 }
2403
2404 $field_array = array_merge( (array) $field->field_options, $field_array );
2405
2406 $values['fields'][ $field->id ] = $field_array;
2407 }
2408
2409 /**
2410 * @since 3.0
2411 *
2412 * @param object $field
2413 *
2414 * @return array
2415 */
2416 public static function start_field_array( $field ) {
2417 return array(
2418 'id' => $field->id,
2419 'default_value' => $field->default_value,
2420 'name' => $field->name,
2421 'description' => $field->description,
2422 'options' => $field->options,
2423 'required' => $field->required,
2424 'field_key' => $field->field_key,
2425 'field_order' => $field->field_order,
2426 'form_id' => $field->form_id,
2427 );
2428 }
2429
2430 /**
2431 * @param object $record
2432 * @param string $table
2433 * @param array $post_values
2434 * @param array $values
2435 */
2436 private static function fill_form_opts( $record, $table, $post_values, array &$values ) {
2437 if ( $table == 'entries' ) {
2438 $form = $record->form_id;
2439 FrmForm::maybe_get_form( $form );
2440 } else {
2441 $form = $record;
2442 }
2443
2444 if ( ! $form ) {
2445 return;
2446 }
2447
2448 $values['form_name'] = isset( $record->form_id ) ? $form->name : '';
2449 $values['parent_form_id'] = isset( $record->form_id ) ? $form->parent_form_id : 0;
2450
2451 if ( ! is_array( $form->options ) ) {
2452 return;
2453 }
2454
2455 foreach ( $form->options as $opt => $value ) {
2456 if ( isset( $post_values[ $opt ] ) ) {
2457 $values[ $opt ] = $post_values[ $opt ];
2458 self::unserialize_or_decode( $values[ $opt ] );
2459 } else {
2460 $values[ $opt ] = $value;
2461 }
2462 }
2463
2464 self::fill_form_defaults( $post_values, $values );
2465 }
2466
2467 /**
2468 * Set to POST value or default
2469 */
2470 private static function fill_form_defaults( $post_values, array &$values ) {
2471 $form_defaults = FrmFormsHelper::get_default_opts();
2472
2473 foreach ( $form_defaults as $opt => $default ) {
2474 if ( ! isset( $values[ $opt ] ) || $values[ $opt ] == '' ) {
2475 $values[ $opt ] = ( $post_values && isset( $post_values['options'][ $opt ] ) ) ? $post_values['options'][ $opt ] : $default;
2476 }
2477
2478 unset( $opt, $default );
2479 }
2480
2481 if ( ! isset( $values['custom_style'] ) ) {
2482 $values['custom_style'] = self::custom_style_value( $post_values );
2483 }
2484
2485 foreach ( array( 'before', 'after', 'submit' ) as $h ) {
2486 if ( ! isset( $values[ $h . '_html' ] ) ) {
2487 $values[ $h . '_html' ] = ( isset( $post_values['options'][ $h . '_html' ] ) ? $post_values['options'][ $h . '_html' ] : FrmFormsHelper::get_default_html( $h ) );
2488 }
2489 unset( $h );
2490 }
2491 }
2492
2493 /**
2494 * @since 2.2.10
2495 *
2496 * @param array $post_values
2497 *
2498 * @return boolean|int
2499 */
2500 public static function custom_style_value( $post_values ) {
2501 if ( ! empty( $post_values ) && isset( $post_values['options']['custom_style'] ) ) {
2502 $custom_style = absint( $post_values['options']['custom_style'] );
2503 } else {
2504 $frm_settings = self::get_settings();
2505 $custom_style = ( $frm_settings->load_style != 'none' );
2506 }
2507
2508 return $custom_style;
2509 }
2510
2511 public static function truncate( $str, $length, $minword = 3, $continue = '...' ) {
2512 if ( is_array( $str ) ) {
2513 return '';
2514 }
2515
2516 $length = (int) $length;
2517 $str = wp_strip_all_tags( $str );
2518 $original_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $str ) );
2519
2520 if ( $length == 0 ) {
2521 return '';
2522 } elseif ( $length <= 10 ) {
2523 $sub = self::mb_function( array( 'mb_substr', 'substr' ), array( $str, 0, $length ) );
2524
2525 return $sub . ( ( $length < $original_len ) ? $continue : '' );
2526 }
2527
2528 $sub = '';
2529 $len = 0;
2530
2531 $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
2532
2533 foreach ( $words as $word ) {
2534 $part = ( ( $sub != '' ) ? ' ' : '' ) . $word;
2535 $total_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub . $part ) );
2536 if ( $total_len > $length && substr_count( $sub, ' ' ) ) {
2537 break;
2538 }
2539
2540 $sub .= $part;
2541 $len += self::mb_function( array( 'mb_strlen', 'strlen' ), array( $part ) );
2542
2543 if ( substr_count( $sub, ' ' ) > $minword && $total_len >= $length ) {
2544 break;
2545 }
2546
2547 unset( $total_len, $word );
2548 }
2549
2550 $sub = self::maybe_force_truncate_on_string_with_no_spaces( $sub, $length );
2551
2552 return $sub . ( ( $len < $original_len ) ? $continue : '' );
2553 }
2554
2555 /**
2556 * If the string is still too long because there may not have been any spaces, force truncate.
2557 *
2558 * @since 6.5.4
2559 *
2560 * @param string $sub Current substring.
2561 * @param int $length The length limit.
2562 * @return string
2563 */
2564 private static function maybe_force_truncate_on_string_with_no_spaces( $sub, $length ) {
2565 if ( strlen( $sub ) < $length + 50 ) {
2566 // If the string isn't way over the limit, leave it.
2567 return $sub;
2568 }
2569
2570 $first_space = strpos( $sub, ' ', $length );
2571 if ( false !== $first_space ) {
2572 // Ignore anything with spaces.
2573 return $sub;
2574 }
2575
2576 return substr( $sub, 0, $length + 10 );
2577 }
2578
2579 public static function mb_function( $function_names, $args ) {
2580 $mb_function_name = $function_names[0];
2581 $function_name = $function_names[1];
2582 if ( function_exists( $mb_function_name ) ) {
2583 $function_name = $mb_function_name;
2584 }
2585
2586 return call_user_func_array( $function_name, $args );
2587 }
2588
2589 public static function get_formatted_time( $date, $date_format = '', $time_format = '' ) {
2590 if ( empty( $date ) ) {
2591 return $date;
2592 }
2593
2594 if ( empty( $date_format ) ) {
2595 $date_format = get_option( 'date_format' );
2596 }
2597
2598 if ( preg_match( '/^\d{1-2}\/\d{1-2}\/\d{4}$/', $date ) && self::pro_is_installed() ) {
2599 $frmpro_settings = new FrmProSettings();
2600 $date = FrmProAppHelper::convert_date( $date, $frmpro_settings->date_format, 'Y-m-d' );
2601 }
2602
2603 $formatted = self::get_localized_date( $date_format, $date );
2604
2605 $do_time = ( gmdate( 'H:i:s', strtotime( $date ) ) != '00:00:00' );
2606 if ( $do_time ) {
2607 $formatted .= self::add_time_to_date( $time_format, $date );
2608 }
2609
2610 return $formatted;
2611 }
2612
2613 private static function add_time_to_date( $time_format, $date ) {
2614 if ( empty( $time_format ) ) {
2615 $time_format = get_option( 'time_format' );
2616 }
2617
2618 $trimmed_format = trim( $time_format );
2619 $time = '';
2620 if ( $time_format && ! empty( $trimmed_format ) ) {
2621 $time = ' ' . __( 'at', 'formidable' ) . ' ' . self::get_localized_date( $time_format, $date );
2622 }
2623
2624 return $time;
2625 }
2626
2627 /**
2628 * @since 2.0.8
2629 */
2630 public static function get_localized_date( $date_format, $date ) {
2631 $date = get_date_from_gmt( $date );
2632
2633 return date_i18n( $date_format, strtotime( $date ) );
2634 }
2635
2636 /**
2637 * Gets the time ago in words.
2638 *
2639 * @param int $from In seconds.
2640 * @param int|string $to In seconds.
2641 *
2642 * @return string $time_ago
2643 */
2644 public static function human_time_diff( $from, $to = '', $levels = 1 ) {
2645 if ( empty( $to ) && 0 !== $to ) {
2646 $now = new DateTime();
2647 } else {
2648 $now = new DateTime( '@' . $to );
2649 }
2650 $ago = new DateTime( '@' . $from );
2651
2652 // Get the time difference
2653 $diff_object = $now->diff( $ago );
2654 $diff = get_object_vars( $diff_object );
2655
2656 // Add week amount and update day amount
2657 $diff['w'] = floor( $diff['d'] / 7 );
2658 $diff['d'] -= $diff['w'] * 7;
2659
2660 $time_strings = self::get_time_strings();
2661
2662 if ( ! is_numeric( $levels ) ) {
2663 // Show time in specified unit.
2664 $levels = self::get_unit( $levels );
2665 if ( isset( $time_strings[ $levels ] ) ) {
2666 $diff = array(
2667 $levels => self::time_format( $levels, $diff ),
2668 );
2669 $time_strings = array(
2670 $levels => $time_strings[ $levels ],
2671 );
2672 }
2673 $levels = 1;
2674 }
2675
2676 foreach ( $time_strings as $k => $v ) {
2677 if ( isset( $diff[ $k ] ) && $diff[ $k ] ) {
2678 $time_strings[ $k ] = $diff[ $k ] . ' ' . ( $diff[ $k ] > 1 ? $v[1] : $v[0] );
2679 } elseif ( isset( $diff[ $k ] ) && count( $time_strings ) === 1 ) {
2680 // Account for 0.
2681 $time_strings[ $k ] = $diff[ $k ] . ' ' . $v[1];
2682 } else {
2683 unset( $time_strings[ $k ] );
2684 }
2685 }
2686
2687 $levels_deep = apply_filters( 'frm_time_ago_levels', $levels, compact( 'time_strings', 'from', 'to' ) );
2688 $time_strings = array_slice( $time_strings, 0, absint( $levels_deep ) );
2689 $time_ago_string = implode( ' ', $time_strings );
2690
2691 return $time_ago_string;
2692 }
2693
2694 /**
2695 * @since 4.05.01
2696 */
2697 private static function time_format( $unit, $diff ) {
2698 $return = array(
2699 'y' => 'y',
2700 'd' => 'days',
2701 );
2702 if ( isset( $return[ $unit ] ) ) {
2703 return $diff[ $return[ $unit ] ];
2704 }
2705
2706 $total = $diff['days'] * self::convert_time( 'd', $unit );
2707
2708 $times = array( 'h', 'i', 's' );
2709
2710 foreach ( $times as $time ) {
2711 if ( ! isset( $diff[ $time ] ) ) {
2712 continue;
2713 }
2714
2715 $total += $diff[ $time ] * self::convert_time( $time, $unit );
2716 }
2717
2718 return floor( $total );
2719 }
2720
2721 /**
2722 * @since 4.05.01
2723 */
2724 private static function convert_time( $from, $to ) {
2725 $convert = array(
2726 's' => 1,
2727 'i' => MINUTE_IN_SECONDS,
2728 'h' => HOUR_IN_SECONDS,
2729 'd' => DAY_IN_SECONDS,
2730 'w' => WEEK_IN_SECONDS,
2731 'm' => DAY_IN_SECONDS * 30.42,
2732 'y' => DAY_IN_SECONDS * 365.25,
2733 );
2734
2735 return $convert[ $from ] / $convert[ $to ];
2736 }
2737
2738 /**
2739 * @since 4.05.01
2740 */
2741 private static function get_unit( $unit ) {
2742 $units = self::get_time_strings();
2743 if ( isset( $units[ $unit ] ) || is_numeric( $unit ) ) {
2744 return $unit;
2745 }
2746
2747 foreach ( $units as $u => $strings ) {
2748 if ( in_array( $unit, $strings ) ) {
2749 return $u;
2750 }
2751 }
2752 return 1;
2753 }
2754
2755 /**
2756 * Get the translatable time strings. The untranslated version is a failsafe
2757 * in case langauges are changing for the unit set in the shortcode.
2758 *
2759 * @since 2.0.20
2760 * @return array
2761 */
2762 private static function get_time_strings() {
2763 return array(
2764 'y' => array(
2765 __( 'year', 'formidable' ),
2766 __( 'years', 'formidable' ),
2767 'year',
2768 ),
2769 'm' => array(
2770 __( 'month', 'formidable' ),
2771 __( 'months', 'formidable' ),
2772 'month',
2773 ),
2774 'w' => array(
2775 __( 'week', 'formidable' ),
2776 __( 'weeks', 'formidable' ),
2777 'week',
2778 ),
2779 'd' => array(
2780 __( 'day', 'formidable' ),
2781 __( 'days', 'formidable' ),
2782 'day',
2783 ),
2784 'h' => array(
2785 __( 'hour', 'formidable' ),
2786 __( 'hours', 'formidable' ),
2787 'hour',
2788 ),
2789 'i' => array(
2790 __( 'minute', 'formidable' ),
2791 __( 'minutes', 'formidable' ),
2792 'minute',
2793 ),
2794 's' => array(
2795 __( 'second', 'formidable' ),
2796 __( 'seconds', 'formidable' ),
2797 'second',
2798 ),
2799 );
2800 }
2801
2802 // Pagination Methods.
2803
2804 /**
2805 * @param integer $r_count
2806 * @param integer $current_p
2807 * @param integer $p_size
2808 * @return int
2809 */
2810 public static function get_last_record_num( $r_count, $current_p, $p_size ) {
2811 return ( ( $r_count < ( $current_p * $p_size ) ) ? $r_count : ( $current_p * $p_size ) );
2812 }
2813
2814 /**
2815 * @param integer $r_count
2816 * @param integer $current_p
2817 * @param integer $p_size
2818 * @return int
2819 */
2820 public static function get_first_record_num( $r_count, $current_p, $p_size ) {
2821 if ( $current_p == 1 ) {
2822 return 1;
2823 } else {
2824 return ( self::get_last_record_num( $r_count, ( $current_p - 1 ), $p_size ) + 1 );
2825 }
2826 }
2827
2828 /**
2829 * @return array
2830 */
2831 public static function json_to_array( $json_vars ) {
2832 $vars = array();
2833 foreach ( $json_vars as $jv ) {
2834 $jv_name = explode( '[', $jv['name'] );
2835 $last = count( $jv_name ) - 1;
2836 foreach ( $jv_name as $p => $n ) {
2837 $name = trim( $n, ']' );
2838 if ( ! isset( $l1 ) ) {
2839 $l1 = $name;
2840 }
2841
2842 if ( ! isset( $l2 ) ) {
2843 $l2 = $name;
2844 }
2845
2846 if ( ! isset( $l3 ) ) {
2847 $l3 = $name;
2848 }
2849
2850 $this_val = ( $p == $last ) ? $jv['value'] : array();
2851
2852 switch ( $p ) {
2853 case 0:
2854 $l1 = $name;
2855 self::add_value_to_array( $name, $l1, $this_val, $vars );
2856 break;
2857
2858 case 1:
2859 $l2 = $name;
2860 self::add_value_to_array( $name, $l2, $this_val, $vars[ $l1 ] );
2861 break;
2862
2863 case 2:
2864 $l3 = $name;
2865 self::add_value_to_array( $name, $l3, $this_val, $vars[ $l1 ][ $l2 ] );
2866 break;
2867
2868 case 3:
2869 $l4 = $name;
2870 self::add_value_to_array( $name, $l4, $this_val, $vars[ $l1 ][ $l2 ][ $l3 ] );
2871 }
2872
2873 unset( $this_val, $n );
2874 }//end foreach
2875
2876 unset( $last, $jv );
2877 }//end foreach
2878
2879 return $vars;
2880 }
2881
2882 /**
2883 * @param string $name
2884 * @param string $l1
2885 */
2886 public static function add_value_to_array( $name, $l1, $val, &$vars ) {
2887 if ( $name == '' ) {
2888 $vars[] = $val;
2889 } elseif ( ! isset( $vars[ $l1 ] ) ) {
2890 $vars[ $l1 ] = $val;
2891 }
2892 }
2893
2894 public static function maybe_add_tooltip( $name, $class = 'closed', $form_name = '' ) {
2895 $tooltips = array(
2896 'action_title' => __( 'Give this action a label for easy reference.', 'formidable' ),
2897 'email_to' => __( 'Add one or more recipient addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com. [admin_email] is the address set in WP General Settings.', 'formidable' ),
2898 'cc' => __( 'Add CC addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2899 'bcc' => __( 'Add BCC addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2900 'reply_to' => __( 'If you would like a different reply to address than the "from" address, add a single address here. FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2901 'from' => __( 'Enter the name and/or email address of the sender. FORMAT: John Bates <john@example.com> or john@example.com.', 'formidable' ),
2902 /* translators: %1$s: Form name, %2$s: Date */
2903 'email_subject' => esc_attr( sprintf( __( 'If you leave the subject blank, the default will be used: %1$s Form submitted on %2$s', 'formidable' ), $form_name, self::site_name() ) ),
2904 'new_tab' => __( 'This option will open the link in a new browser tab. Please note that some popup blockers may prevent this from happening, in which case the link will be displayed.', 'formidable' ),
2905 );
2906
2907 if ( ! isset( $tooltips[ $name ] ) ) {
2908 return;
2909 }
2910
2911 if ( 'open' == $class ) {
2912 echo ' frm_help"';
2913 } else {
2914 echo ' class="frm_help"';
2915 }
2916
2917 echo ' title="' . esc_attr( $tooltips[ $name ] );
2918
2919 if ( 'open' != $class ) {
2920 echo '"';
2921 }
2922 }
2923
2924 /**
2925 * Add the current_page class to that page in the form nav
2926 */
2927 public static function select_current_page( $page, $current_page, $action = array() ) {
2928 if ( $current_page != $page ) {
2929 return;
2930 }
2931
2932 $frm_action = self::simple_get( 'frm_action', 'sanitize_title' );
2933 if ( 'lite-reports' === $frm_action ) {
2934 $frm_action = 'reports';
2935 }
2936
2937 if ( empty( $action ) || ( ! empty( $frm_action ) && in_array( $frm_action, $action ) ) ) {
2938 echo ' class="current_page"';
2939 }
2940 }
2941
2942 /**
2943 * Prepare and json_encode post content
2944 *
2945 * @since 2.0
2946 *
2947 * @param array $post_content
2948 *
2949 * @return string $post_content ( json encoded array )
2950 */
2951 public static function prepare_and_encode( $post_content ) {
2952 // Loop through array to strip slashes and add only the needed ones.
2953 foreach ( $post_content as $key => $val ) {
2954 // Replace problematic characters (like &quot;)
2955 if ( is_string( $val ) ) {
2956 $val = str_replace( '&quot;', '"', $val );
2957 }
2958
2959 self::prepare_action_slashes( $val, $key, $post_content );
2960 unset( $key, $val );
2961 }
2962
2963 // json_encode the array.
2964 $post_content = json_encode( $post_content );
2965
2966 // Add extra slashes for \r\n since WP strips them.
2967 $post_content = str_replace( array( '\\r', '\\n', '\\u', '\\t' ), array( '\\\\r', '\\\\n', '\\\\u', '\\\\t' ), $post_content );
2968
2969 // allow for &quot
2970 $post_content = str_replace( '&quot;', '\\"', $post_content );
2971
2972 return $post_content;
2973 }
2974
2975 private static function prepare_action_slashes( $val, $key, &$post_content ) {
2976 if ( ! isset( $post_content[ $key ] ) || is_numeric( $val ) ) {
2977 return;
2978 }
2979
2980 if ( is_array( $val ) ) {
2981 foreach ( $val as $k1 => $v1 ) {
2982 self::prepare_action_slashes( $v1, $k1, $post_content[ $key ] );
2983 unset( $k1, $v1 );
2984 }
2985 } else {
2986 // Strip all slashes so everything is the same, no matter where the value is coming from
2987 $val = stripslashes( $val );
2988
2989 // Add backslashes before double quotes and forward slashes only
2990 $post_content[ $key ] = addcslashes( $val, '"\\/' );
2991 }
2992 }
2993
2994 /**
2995 * Check for either json or serialized data. This is temporary while transitioning
2996 * all data to json.
2997 *
2998 * @since 4.02.03
2999 *
3000 * @param array|string $value
3001 * @return void
3002 */
3003 public static function unserialize_or_decode( &$value ) {
3004 if ( is_array( $value ) ) {
3005 return;
3006 }
3007
3008 if ( is_serialized( $value ) ) {
3009 $value = self::maybe_unserialize_array( $value );
3010 } else {
3011 $value = self::maybe_json_decode( $value, false );
3012 }
3013 }
3014
3015 /**
3016 * Safely unserialize an array if necessary.
3017 * This function doesn't actually use unserialize. The string is parsed instead.
3018 *
3019 * @since 6.2
3020 *
3021 * @param mixed $value
3022 * @return mixed
3023 */
3024 public static function maybe_unserialize_array( $value ) {
3025 if ( ! is_string( $value ) ) {
3026 return $value;
3027 }
3028
3029 // Since we only expect an array, skip anything that doesn't start with a:.
3030 if ( ! is_serialized( $value ) || 'a:' !== substr( $value, 0, 2 ) ) {
3031 return $value;
3032 }
3033
3034 $parsed = FrmSerializedStringParserHelper::get()->parse( $value );
3035 if ( is_array( $parsed ) ) {
3036 $value = $parsed;
3037 }
3038
3039 return $value;
3040 }
3041
3042 /**
3043 * Decode a JSON string.
3044 * Do not switch shortcodes like [24] to array unless intentional ie XML values.
3045 *
3046 * @param mixed $string
3047 * @param bool $single_to_array
3048 * @return mixed
3049 */
3050 public static function maybe_json_decode( $string, $single_to_array = true ) {
3051 if ( is_array( $string ) || is_null( $string ) ) {
3052 return $string;
3053 }
3054
3055 $new_string = json_decode( $string, true );
3056 if ( function_exists( 'json_last_error' ) ) {
3057 // php 5.3+
3058 $single_value = false;
3059 if ( ! $single_to_array ) {
3060 $single_value = is_array( $new_string ) && count( $new_string ) === 1 && isset( $new_string[0] );
3061 }
3062 if ( json_last_error() == JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
3063 $string = $new_string;
3064 }
3065 }
3066
3067 return $string;
3068 }
3069
3070 /**
3071 * @since 6.2.3
3072 *
3073 * @param string $value
3074 * @return string
3075 */
3076 public static function maybe_utf8_encode( $value ) {
3077 $from_format = 'ISO-8859-1';
3078 $to_format = 'UTF-8';
3079
3080 if ( function_exists( 'mb_check_encoding' ) && function_exists( 'mb_convert_encoding' ) ) {
3081 if ( mb_check_encoding( $value, $from_format ) ) {
3082 return mb_convert_encoding( $value, $to_format, $from_format );
3083 }
3084 return $value;
3085 }
3086
3087 if ( function_exists( 'iconv' ) ) {
3088 $converted = iconv( $from_format, $to_format, $value );
3089 // Value is false if $value is not ISO-8859-1.
3090 if ( false !== $converted ) {
3091 return $converted;
3092 }
3093 }
3094
3095 return $value;
3096 }
3097
3098 /**
3099 * Reformat the json serialized array in name => value array.
3100 *
3101 * @since 4.02.03
3102 */
3103 public static function format_form_data( &$form ) {
3104 $formatted = array();
3105
3106 foreach ( $form as $input ) {
3107 if ( ! isset( $input['name'] ) ) {
3108 continue;
3109 }
3110 $key = $input['name'];
3111 if ( isset( $formatted[ $key ] ) ) {
3112 if ( is_array( $formatted[ $key ] ) ) {
3113 $formatted[ $key ][] = $input['value'];
3114 } else {
3115 $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
3116 }
3117 } else {
3118 $formatted[ $key ] = $input['value'];
3119 }
3120 }
3121
3122 parse_str( http_build_query( $formatted ), $form );
3123 }
3124
3125 /**
3126 * @since 4.02.03
3127 */
3128 public static function maybe_json_encode( $value ) {
3129 if ( is_array( $value ) ) {
3130 $value = wp_json_encode( $value );
3131 }
3132 return $value;
3133 }
3134
3135 /**
3136 * Echo The javascript to open and highlight the Formidable menu
3137 *
3138 * @since 1.07.10
3139 *
3140 * @param string $post_type The name of the post type that may need to be highlighted.
3141 * @return void
3142 */
3143 public static function maybe_highlight_menu( $post_type ) {
3144 global $post;
3145
3146 if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] != $post_type ) {
3147 return;
3148 }
3149
3150 if ( is_object( $post ) && $post->post_type != $post_type ) {
3151 return;
3152 }
3153
3154 self::load_admin_wide_js();
3155 echo '<script type="text/javascript">jQuery(document).ready(function(){frmSelectSubnav();});</script>';
3156 }
3157
3158 /**
3159 * Load the JS file on non-Formidable pages in the admin area
3160 *
3161 * @since 2.0
3162 *
3163 * @param bool $load
3164 * @return void
3165 */
3166 public static function load_admin_wide_js( $load = true ) {
3167 $version = self::plugin_version();
3168 wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), $version );
3169
3170 $global_strings = array(
3171 'updating_msg' => __( 'Please wait while your site updates.', 'formidable' ),
3172 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
3173 'url' => self::plugin_url(),
3174 'app_url' => 'https://formidableforms.com/',
3175 'applicationsUrl' => admin_url( 'admin.php?page=formidable-applications' ),
3176 'canAccessApplicationDashboard' => current_user_can( is_callable( 'FrmProApplicationsHelper::get_required_templates_capability' ) ? FrmProApplicationsHelper::get_required_templates_capability() : 'frm_edit_forms' ),
3177 'loading' => __( 'Loading&hellip;', 'formidable' ),
3178 'nonce' => wp_create_nonce( 'frm_ajax' ),
3179 'proIncludesSliderJs' => is_callable( 'FrmProFormsHelper::prepare_custom_currency' ),
3180 'inboxSlideIn' => FrmInbox::get_inbox_slide_in_value_for_js(),
3181 );
3182 wp_localize_script( 'formidable_admin_global', 'frmGlobal', $global_strings );
3183
3184 if ( $load ) {
3185 wp_enqueue_script( 'formidable_admin_global' );
3186 }
3187 }
3188
3189 /**
3190 * @since 2.0.9
3191 */
3192 public static function load_font_style() {
3193 wp_enqueue_style( 'frm_fonts', self::plugin_url() . '/css/frm_fonts.css', array(), self::plugin_version() );
3194 }
3195
3196 /**
3197 * @param string $location
3198 */
3199 public static function localize_script( $location ) {
3200 global $wp_scripts;
3201
3202 $script_strings = array(
3203 'ajax_url' => esc_url_raw( self::get_ajax_url() ),
3204 'images_url' => self::plugin_url() . '/images',
3205 'loading' => __( 'Loading&hellip;', 'formidable' ),
3206 'remove' => __( 'Remove', 'formidable' ),
3207 'offset' => apply_filters( 'frm_scroll_offset', 4 ),
3208 'nonce' => wp_create_nonce( 'frm_ajax' ),
3209 'id' => __( 'ID', 'formidable' ),
3210 'no_results' => __( 'No results match', 'formidable' ),
3211 'file_spam' => __( 'That file looks like Spam.', 'formidable' ),
3212 'calc_error' => __( 'There is an error in the calculation in the field with key', 'formidable' ),
3213 'empty_fields' => __( 'Please complete the preceding required fields before uploading a file.', 'formidable' ),
3214 'focus_first_error' => self::should_focus_first_error(),
3215 'include_alert_role' => self::should_include_alert_role_on_field_errors(),
3216 );
3217
3218 $data = $wp_scripts->get_data( 'formidable', 'data' );
3219 if ( ! $data ) {
3220 wp_localize_script( 'formidable', 'frm_js', $script_strings );
3221 }
3222
3223 if ( $location === 'admin' ) {
3224 $admin_script_strings = array(
3225 'desc' => __( '(Click to add description)', 'formidable' ),
3226 'blank' => __( '(Blank)', 'formidable' ),
3227 'no_label' => __( '(no label)', 'formidable' ),
3228 'ok' => __( 'OK', 'formidable' ),
3229 'cancel' => __( 'Cancel', 'formidable' ),
3230 'default_label' => __( 'Default', 'formidable' ),
3231 'clear_default' => __( 'Clear default value when typing', 'formidable' ),
3232 'no_clear_default' => __( 'Do not clear default value when typing', 'formidable' ),
3233 'valid_default' => __( 'Default value will pass form validation', 'formidable' ),
3234 'no_valid_default' => __( 'Default value will NOT pass form validation', 'formidable' ),
3235 'confirm' => __( 'Are you sure?', 'formidable' ),
3236 'conf_delete' => __( 'Are you sure you want to delete this field and all data associated with it?', 'formidable' ),
3237 'conf_delete_sec' => __( 'All fields inside this Section will be deleted along with their data. Are you sure you want to delete this group of fields?', 'formidable' ),
3238 'conf_no_repeat' => __( 'Warning: If you have entries with multiple rows, all but the first row will be lost.', 'formidable' ),
3239 'default_unique' => FrmFieldsHelper::default_unique_msg(),
3240 'default_conf' => __( 'The entered values do not match', 'formidable' ),
3241 'enter_email' => __( 'Enter Email', 'formidable' ),
3242 'confirm_email' => __( 'Confirm Email', 'formidable' ),
3243 'conditional_text' => __( 'Conditional content here', 'formidable' ),
3244 'new_option' => __( 'New Option', 'formidable' ),
3245 'css_invalid_size' => __( 'In certain browsers (e.g. Firefox) text will not display correctly if the field height is too small relative to the field padding and text size. Please increase your field height or decrease your field padding.', 'formidable' ),
3246 'enter_password' => __( 'Enter Password', 'formidable' ),
3247 'confirm_password' => __( 'Confirm Password', 'formidable' ),
3248 'import_complete' => __( 'Import Complete', 'formidable' ),
3249 'updating' => __( 'Please wait while your site updates.', 'formidable' ),
3250 'no_save_warning' => __( 'Warning: There is no way to retrieve unsaved entries.', 'formidable' ),
3251 'private_label' => __( 'Private', 'formidable' ),
3252 'jquery_ui_url' => '',
3253 'pro_url' => is_callable( 'FrmProAppHelper::plugin_url' ) ? FrmProAppHelper::plugin_url() : '',
3254 'no_licenses' => __( 'No new licenses were found', 'formidable' ),
3255 'unmatched_parens' => __( 'This calculation has at least one unmatched ( ) { } [ ].', 'formidable' ),
3256 'view_shortcodes' => __( 'This calculation may have shortcodes that work in Views but not forms.', 'formidable' ),
3257 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
3258 /* translators: %d is the number of allowed actions per form */
3259 'only_one_action' => sprintf( __( 'This form action is limited to %d per form.', 'formidable' ), 1 ),
3260 'edit_action_text' => __( 'Please edit the existing form action.', 'formidable' ),
3261 'unsafe_params' => FrmFormsHelper::reserved_words(),
3262 /* Translators: %s is the name of a Detail Page Slug that is a reserved word.*/
3263 'slug_is_reserved' => sprintf( __( 'The Detail Page Slug "%s" is reserved by WordPress. This may cause problems. Is this intentional?', 'formidable' ), '****' ),
3264 /* Translators: %s is the name of a parameter that is a reserved word. More than one word could be listed here, though that would not be common. */
3265 'param_is_reserved' => sprintf( __( 'The parameter "%s" is reserved by WordPress. This may cause problems when included in the URL. Is this intentional? ', 'formidable' ), '****' ),
3266 'reserved_words' => __( 'See the list of reserved words in WordPress.', 'formidable' ),
3267 'repeat_limit_min' => __( 'Please enter a Repeat Limit that is greater than 1.', 'formidable' ),
3268 'checkbox_limit' => __( 'Please select a limit between 0 and 200.', 'formidable' ),
3269 'install' => __( 'Install', 'formidable' ),
3270 'active' => __( 'Active', 'formidable' ),
3271 'installed' => __( 'Installed', 'formidable' ),
3272 'not_installed' => __( 'Not Installed', 'formidable' ),
3273 'select_a_field' => __( 'Select a Field', 'formidable' ),
3274 'no_items_found' => __( 'No items found.', 'formidable' ),
3275 'field_already_used' => __( 'Oops. You have already used that field.', 'formidable' ),
3276
3277 // Deprecated in 6.0.
3278 'saving' => '',
3279
3280 // Deprecated in 6.0.
3281 'saved' => '',
3282
3283 // translators: %1$s: HTML open tag, %2$s: HTML end tag.
3284 'holdShiftMsg' => esc_html__( 'You can hold %1$sShift%2$s on your keyboard to select multiple fields', 'formidable' ),
3285 );
3286 /**
3287 * @param array $admin_script_strings
3288 */
3289 $admin_script_strings = apply_filters( 'frm_admin_script_strings', $admin_script_strings );
3290
3291 $data = $wp_scripts->get_data( 'formidable_admin', 'data' );
3292 if ( ! $data ) {
3293 wp_localize_script( 'formidable_admin', 'frm_admin_js', $admin_script_strings );
3294 }
3295 }//end if
3296 }
3297
3298 /**
3299 * @since 6.5
3300 *
3301 * @return string
3302 */
3303 public static function get_ajax_url() {
3304 $ajax_url = admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' );
3305
3306 /**
3307 * @since 2.0.13
3308 *
3309 * @param string $ajax_url
3310 */
3311 return apply_filters( 'frm_ajax_url', $ajax_url );
3312 }
3313
3314 /**
3315 * Returns whether or not the first errored input should be auto-focused (default true).
3316 *
3317 * @since 5.2.05
3318 *
3319 * @return bool
3320 */
3321 private static function should_focus_first_error() {
3322 return (bool) apply_filters( 'frm_focus_first_error', true );
3323 }
3324
3325 /**
3326 * Returns whether or not field errors should include role="alert" (default true).
3327 *
3328 * @since 5.2.05
3329 *
3330 * @return bool
3331 */
3332 public static function should_include_alert_role_on_field_errors() {
3333 return (bool) apply_filters( 'frm_include_alert_role_on_field_errors', true );
3334 }
3335
3336 /**
3337 * Echo the message on the plugins listing page
3338 *
3339 * @since 1.07.10
3340 *
3341 * @param float $min_version The version the add-on requires.
3342 */
3343 public static function min_version_notice( $min_version ) {
3344 $frm_version = self::plugin_version();
3345
3346 // Check if Formidable meets minimum requirements.
3347 if ( version_compare( $frm_version, $min_version, '>=' ) ) {
3348 return;
3349 }
3350
3351 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
3352 echo '<tr class="plugin-update-tr active"><th colspan="' . absint( $wp_list_table->get_column_count() ) . '" class="check-column plugin-update colspanchange"><div class="update-message">' .
3353 esc_html__( 'You are running an outdated version of Formidable. This plugin may not work correctly if you do not update Formidable.', 'formidable' ) .
3354 '</div></td></tr>';
3355 }
3356
3357 /**
3358 * If Pro is far outdated, show a message.
3359 *
3360 * @since 4.0.01
3361 *
3362 * @return void
3363 */
3364 public static function min_pro_version_notice( $min_version ) {
3365 if ( ! self::is_formidable_admin() ) {
3366 // Don't show admin-wide.
3367 return;
3368 }
3369
3370 self::php_version_notice();
3371
3372 $is_pro = self::pro_is_installed() && class_exists( 'FrmProDb' );
3373 if ( ! $is_pro || self::meets_min_pro_version( $min_version ) ) {
3374 return;
3375 }
3376
3377 $pro_version = FrmProDb::$plug_version;
3378 $expired = FrmAddonsController::is_license_expired();
3379 ?>
3380 <div class="frm-banner-alert frm_error_style frm_previous_install">
3381 <?php
3382 esc_html_e( 'You are running a version of Formidable Forms that may not be compatible with your version of Formidable Forms Pro.', 'formidable' );
3383 if ( empty( $expired ) ) {
3384 echo ' Please <a href="' . esc_url( admin_url( 'plugins.php?s=formidable%20forms%20pro' ) ) . '">update now</a>.';
3385 } else {
3386 echo '<br/>Please <a href="https://formidableforms.com/account/downloads/?utm_source=WordPress&utm_medium=outdated">renew now</a> to get the latest version.';
3387 }
3388 ?>
3389 </div>
3390 <?php
3391 }
3392
3393 /**
3394 * If Pro is installed, check the version number.
3395 *
3396 * @since 4.0.01
3397 */
3398 public static function meets_min_pro_version( $min_version ) {
3399 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
3400 }
3401
3402 /**
3403 * Show a message if the browser or PHP version is below the recommendations.
3404 *
3405 * @since 4.0.02
3406 */
3407 private static function php_version_notice() {
3408 $message = array();
3409 if ( version_compare( phpversion(), '5.6', '<' ) ) {
3410 $message[] = __( 'The version of PHP on your server is too low. If this is not corrected, you may see issues with Formidable Forms. Please contact your web host and ask to be updated to PHP 7.0+.', 'formidable' );
3411 }
3412
3413 $browser = self::get_server_value( 'HTTP_USER_AGENT' );
3414 $is_ie = strpos( $browser, 'MSIE' ) !== false;
3415 if ( $is_ie ) {
3416 $message[] = __( 'You are using an outdated browser that is not compatible with Formidable Forms. Please update to a more current browser (we recommend Chrome).', 'formidable' );
3417 }
3418
3419 foreach ( $message as $m ) {
3420 ?>
3421 <div class="frm-banner-alert frm_error_style frm_previous_install">
3422 <?php echo esc_html( $m ); ?>
3423 </div>
3424 <?php
3425 }
3426 }
3427
3428 /**
3429 * @param string $type
3430 * @return array<string,string>
3431 */
3432 public static function locales( $type = 'date' ) {
3433 $locales = array(
3434 'en' => __( 'English', 'formidable' ),
3435 'af' => __( 'Afrikaans', 'formidable' ),
3436 'sq' => __( 'Albanian', 'formidable' ),
3437 'ar-DZ' => __( 'Algerian Arabic', 'formidable' ),
3438 'am' => __( 'Amharic', 'formidable' ),
3439 'ar' => __( 'Arabic', 'formidable' ),
3440 'hy' => __( 'Armenian', 'formidable' ),
3441 'az' => __( 'Azerbaijani', 'formidable' ),
3442 'eu' => __( 'Basque', 'formidable' ),
3443 'be' => __( 'Belarusian', 'formidable' ),
3444 'bn' => __( 'Bengali', 'formidable' ),
3445 'bs' => __( 'Bosnian', 'formidable' ),
3446 'bg' => __( 'Bulgarian', 'formidable' ),
3447 'ca' => __( 'Catalan', 'formidable' ),
3448 'zh-HK' => __( 'Chinese Hong Kong', 'formidable' ),
3449 'zh-CN' => __( 'Chinese Simplified', 'formidable' ),
3450 'zh-TW' => __( 'Chinese Traditional', 'formidable' ),
3451 'hr' => __( 'Croatian', 'formidable' ),
3452 'cs' => __( 'Czech', 'formidable' ),
3453 'da' => __( 'Danish', 'formidable' ),
3454 'nl' => __( 'Dutch', 'formidable' ),
3455 'en-GB' => __( 'English/UK', 'formidable' ),
3456 'eo' => __( 'Esperanto', 'formidable' ),
3457 'et' => __( 'Estonian', 'formidable' ),
3458 'fo' => __( 'Faroese', 'formidable' ),
3459 'fa' => __( 'Farsi/Persian', 'formidable' ),
3460 'fil' => __( 'Filipino', 'formidable' ),
3461 'fi' => __( 'Finnish', 'formidable' ),
3462 'fr' => __( 'French', 'formidable' ),
3463 'fr-CA' => __( 'French/Canadian', 'formidable' ),
3464 'fr-CH' => __( 'French/Swiss', 'formidable' ),
3465 'gl' => __( 'Galician', 'formidable' ),
3466 'ka' => __( 'Georgian', 'formidable' ),
3467 'de' => __( 'German', 'formidable' ),
3468 'de-AT' => __( 'German/Austria', 'formidable' ),
3469 'de-CH' => __( 'German/Switzerland', 'formidable' ),
3470 'el' => __( 'Greek', 'formidable' ),
3471 'gu' => __( 'Gujarati', 'formidable' ),
3472 'he' => __( 'Hebrew', 'formidable' ),
3473 'iw' => __( 'Hebrew', 'formidable' ),
3474 'hi' => __( 'Hindi', 'formidable' ),
3475 'hu' => __( 'Hungarian', 'formidable' ),
3476 'is' => __( 'Icelandic', 'formidable' ),
3477 'id' => __( 'Indonesian', 'formidable' ),
3478 'it' => __( 'Italian', 'formidable' ),
3479 'ja' => __( 'Japanese', 'formidable' ),
3480 'kn' => __( 'Kannada', 'formidable' ),
3481 'kk' => __( 'Kazakh', 'formidable' ),
3482 'km' => __( 'Khmer', 'formidable' ),
3483 'ko' => __( 'Korean', 'formidable' ),
3484 'ky' => __( 'Kyrgyz', 'formidable' ),
3485 'lo' => __( 'Laothian', 'formidable' ),
3486 'lv' => __( 'Latvian', 'formidable' ),
3487 'lt' => __( 'Lithuanian', 'formidable' ),
3488 'lb' => __( 'Luxembourgish', 'formidable' ),
3489 'mk' => __( 'Macedonian', 'formidable' ),
3490 'ml' => __( 'Malayalam', 'formidable' ),
3491 'ms' => __( 'Malaysian', 'formidable' ),
3492 'mr' => __( 'Marathi', 'formidable' ),
3493 'no' => __( 'Norwegian', 'formidable' ),
3494 'nb' => __( 'Norwegian Bokmål', 'formidable' ),
3495 'nn' => __( 'Norwegian Nynorsk', 'formidable' ),
3496 'pl' => __( 'Polish', 'formidable' ),
3497 'pt' => __( 'Portuguese', 'formidable' ),
3498 'pt-BR' => __( 'Portuguese/Brazilian', 'formidable' ),
3499 'pt-PT' => __( 'Portuguese/Portugal', 'formidable' ),
3500 'rm' => __( 'Romansh', 'formidable' ),
3501 'ro' => __( 'Romanian', 'formidable' ),
3502 'ru' => __( 'Russian', 'formidable' ),
3503 'sr' => __( 'Serbian', 'formidable' ),
3504 'sr-SR' => __( 'Serbian', 'formidable' ),
3505 'si' => __( 'Sinhalese', 'formidable' ),
3506 'sk' => __( 'Slovak', 'formidable' ),
3507 'sl' => __( 'Slovenian', 'formidable' ),
3508 'es' => __( 'Spanish', 'formidable' ),
3509 'es-419' => __( 'Spanish/Latin America', 'formidable' ),
3510 'sw' => __( 'Swahili', 'formidable' ),
3511 'sv' => __( 'Swedish', 'formidable' ),
3512 'ta' => __( 'Tamil', 'formidable' ),
3513 'te' => __( 'Telugu', 'formidable' ),
3514 'th' => __( 'Thai', 'formidable' ),
3515 'tj' => __( 'Tajiki', 'formidable' ),
3516 'tr' => __( 'Turkish', 'formidable' ),
3517 'uk' => __( 'Ukrainian', 'formidable' ),
3518 'ur' => __( 'Urdu', 'formidable' ),
3519 'vi' => __( 'Vietnamese', 'formidable' ),
3520 'cy-GB' => __( 'Welsh', 'formidable' ),
3521 'zu' => __( 'Zulu', 'formidable' ),
3522 );
3523
3524 if ( $type === 'captcha' ) {
3525 // remove the languages unavailable for the captcha
3526 $unset = array( 'sq', 'bs', 'eo', 'fo', 'fr-CH', 'sr-SR', 'ar-DZ', 'be', 'cy-GB', 'kk', 'km', 'ky', 'lb', 'mk', 'nb', 'nn', 'rm', 'tj' );
3527 } else {
3528 // remove the languages unavailable for the datepicker
3529 $unset = array( 'fil', 'fr-CA', 'de-AT', 'de-CH', 'iw', 'hi', 'pt', 'pt-PT', 'es-419', 'mr', 'lo', 'kn', 'si', 'gu', 'bn', 'zu', 'ur', 'te', 'sw', 'am' );
3530 }
3531
3532 $locales = array_diff_key( $locales, array_flip( $unset ) );
3533
3534 /**
3535 * Filter available locale options.
3536 *
3537 * @since 5.4.5 Added $args parameter with type.
3538 *
3539 * @param array<string,string> $locales
3540 * @param array $args {
3541 * @type string $type
3542 * }
3543 */
3544 $locales = apply_filters( 'frm_locales', $locales, compact( 'type' ) );
3545
3546 return $locales;
3547 }
3548
3549 /**
3550 * Output HTML containing reference text for accessibility
3551 *
3552 * @deprecated 5.1
3553 */
3554 public static function multiselect_accessibility() {
3555 _deprecated_function( __METHOD__, '5.1' );
3556 }
3557
3558 /**
3559 * @return string
3560 */
3561 public static function get_menu_icon_class() {
3562 if ( is_callable( 'FrmProAppHelper::get_settings' ) ) {
3563 $settings = FrmProAppHelper::get_settings();
3564 if ( is_object( $settings ) && ! empty( $settings->menu_icon ) ) {
3565 return $settings->menu_icon;
3566 }
3567 }
3568 return 'frmfont frm_logo_icon';
3569 }
3570
3571 /**
3572 * Shows the images dropdown.
3573 *
3574 * @since 5.0.04
3575 *
3576 * @param array $args {
3577 * Arguments.
3578 *
3579 * @type string $selected Selected value.
3580 * @type array[] $options Array of options with keys are option values and values are array.
3581 * The option array contains `text`, `svg` and `custom_atts`.
3582 * @type string $classes Custom CSS classes for the wrapper element.
3583 * @type array $input_attrs Attributes of value input.
3584 * }
3585 */
3586 public static function images_dropdown( $args ) {
3587 $args = self::fill_default_images_dropdown_args( $args );
3588
3589 $input_attrs_str = self::get_images_dropdown_input_attrs( $args );
3590 ob_start();
3591 include self::plugin_path() . '/classes/views/shared/images-dropdown.php';
3592 $output = ob_get_clean();
3593
3594 /**
3595 * Allows modifying the output of FrmAppHelper::images_dropdown() method.
3596 *
3597 * @since 5.0.04
3598 *
3599 * @param string $output The output.
3600 * @param array $args Passed arguments.
3601 */
3602 echo apply_filters( 'frm_images_dropdown_output', $output, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3603 }
3604
3605 /**
3606 * Fills the default images_dropdown() arguments.
3607 *
3608 * @since 5.0.04
3609 *
3610 * @param array $args The arguments.
3611 * @return array
3612 */
3613 private static function fill_default_images_dropdown_args( $args ) {
3614 $defaults = array(
3615 'selected' => '',
3616 'options' => array(),
3617 'classes' => '',
3618 'input_attrs' => array(),
3619 );
3620 $new_args = wp_parse_args( $args, $defaults );
3621
3622 $new_args['options'] = (array) $new_args['options'];
3623 $new_args['input_attrs'] = (array) $new_args['input_attrs'];
3624
3625 // Set the number of columns.
3626 $new_args['col_class'] = ceil( 12 / count( $new_args['options'] ) );
3627 if ( $new_args['col_class'] > 6 ) {
3628 $new_args['col_class'] = ceil( $new_args['col_class'] / 2 );
3629 }
3630
3631 /**
3632 * Allows modifying the arguments of images_dropdown() method.
3633 *
3634 * @since 5.0.04
3635 *
3636 * @param array $new_args Arguments after filling the defaults.
3637 * @param array $args Arguments passed to the method, before filling the defaults.
3638 */
3639 return apply_filters( 'frm_images_dropdown_args', $new_args, $args );
3640 }
3641
3642 /**
3643 * Gets HTML attributes of the input in images_dropdown() method.
3644 *
3645 * @since 5.0.04
3646 *
3647 * @param array $args The arguments.
3648 * @return string
3649 */
3650 private static function get_images_dropdown_input_attrs( $args ) {
3651 $input_attrs = $args['input_attrs'];
3652 $input_attrs['type'] = 'radio';
3653 $input_attrs['name'] = $args['name'];
3654
3655 $input_attrs_str = '';
3656 foreach ( $input_attrs as $key => $input_attr ) {
3657 $input_attrs_str .= ' ' . sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $input_attr ) );
3658 }
3659
3660 /**
3661 * Allows modifying the HTML attributes of the input in images_dropdown() method.
3662 *
3663 * @since 5.0.04
3664 *
3665 * @param string $input_attrs_str HTML attributes string.
3666 * @param array $args The arguments of images_dropdown() method.
3667 */
3668 return apply_filters( 'frm_images_dropdown_input_attrs', $input_attrs_str, $args );
3669 }
3670
3671 /**
3672 * @since 6.7.1
3673 */
3674 public static function get_images_dropdown_atts( $option, $args ) {
3675 $image = self::get_images_dropdown_option_image( $option, $args );
3676 $classes = self::get_images_dropdown_option_classes( $option, $args );
3677 $custom_attrs = self::get_images_dropdown_option_html_attrs( $option, $args );
3678 return compact( 'image', 'classes', 'custom_attrs' );
3679 }
3680
3681 /**
3682 * Gets the image of each option in images_dropdown() method.
3683 *
3684 * @since 5.0.04
3685 *
3686 * @param array $option Option data.
3687 * @param array $args The arguments of images_dropdown() method.
3688 * @return string
3689 */
3690 private static function get_images_dropdown_option_image( $option, $args ) {
3691 $image = self::icon_by_class(
3692 'frmfont ' . $option['svg'],
3693 array(
3694 'echo' => false,
3695 )
3696 );
3697
3698 $args['option'] = $option;
3699
3700 /**
3701 * Allows modifying the image of each option in images_dropdown() method.
3702 *
3703 * @since 5.0.04
3704 *
3705 * @param string $image The image HTML.
3706 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3707 */
3708 return apply_filters( 'frm_images_dropdown_option_image', $image, $args );
3709 }
3710
3711 /**
3712 * Gets the HTML classes of each option in images_dropdown() method.
3713 *
3714 * @since 5.0.04
3715 *
3716 * @param array $option Option data.
3717 * @param array $args The arguments of images_dropdown() method.
3718 * @return string
3719 */
3720 private static function get_images_dropdown_option_classes( $option, $args ) {
3721 $classes = '';
3722
3723 if ( ! empty( $option['custom_attrs']['class'] ) ) {
3724 $classes .= ' ' . $option['custom_attrs']['class'];
3725 }
3726
3727 $args['option'] = $option;
3728
3729 /**
3730 * Allows modifying the CSS classes of each option in images_dropdown() method.
3731 *
3732 * @since 5.0.04
3733 *
3734 * @param string $classes CSS classes.
3735 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3736 */
3737 return apply_filters( 'frm_images_dropdown_option_classes', $classes, $args );
3738 }
3739
3740 /**
3741 * Gets the custom HTML attributes of each option in images_dropdown() method.
3742 *
3743 * @since 5.0.04
3744 *
3745 * @param array $option Option data.
3746 * @param array $args The arguments of images_dropdown() method.
3747 * @return string
3748 */
3749 private static function get_images_dropdown_option_html_attrs( $option, $args ) {
3750 $html_attrs = '';
3751 if ( ! empty( $option['custom_attrs'] ) && is_array( $option['custom_attrs'] ) ) {
3752 $html_attrs_arr = array();
3753
3754 foreach ( $option['custom_attrs'] as $key => $value ) {
3755 if ( in_array( $key, array( 'type', 'class', 'data-value' ) ) ) {
3756 continue;
3757 }
3758
3759 $html_attrs_arr[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
3760 }
3761
3762 $html_attrs = implode( ' ', $html_attrs_arr );
3763 }
3764
3765 $args['option'] = $option;
3766
3767 /**
3768 * Allows modifying the custom HTML attributes of each option in images_dropdown() method.
3769 *
3770 * @since 5.0.04
3771 *
3772 * @param string $html_attrs The HTML attributes string.
3773 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3774 */
3775 return apply_filters( 'frm_images_dropdown_option_html_attrs', $html_attrs, $args );
3776 }
3777
3778 /**
3779 * @since 5.0.07
3780 *
3781 * @return bool true if the current user is allowed to save unfiltered HTML.
3782 */
3783 public static function allow_unfiltered_html() {
3784 if ( self::should_never_allow_unfiltered_html() ) {
3785 return false;
3786 }
3787 return current_user_can( 'unfiltered_html' );
3788 }
3789
3790 /**
3791 * @since 5.0.13
3792 *
3793 * @return bool
3794 */
3795 public static function should_never_allow_unfiltered_html() {
3796 if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) {
3797 return true;
3798 }
3799
3800 /**
3801 * Formidable will check DISALLOW_UNFILTERED_HTML to determine if some form HTML should be filtered or not.
3802 * In many cases, scripts are added intentionally to forms and will not be stripped if DISALLOW_UNFILTERED_HTML is not set.
3803 * It is also possible to filter Formidable without defining DISALLOW_UNFILTERED_HTML, with add_filter( 'frm_disallow_unfiltered_html', '__return_true' );
3804 *
3805 * @since 5.0.13
3806 */
3807 return apply_filters( 'frm_disallow_unfiltered_html', false );
3808 }
3809
3810 /**
3811 * @since 5.0.07
3812 *
3813 * @param array $values
3814 * @param array $keys
3815 * @return array
3816 */
3817 public static function maybe_filter_array( $values, $keys ) {
3818 $allow_unfiltered_html = self::allow_unfiltered_html();
3819
3820 if ( $allow_unfiltered_html ) {
3821 return $values;
3822 }
3823
3824 foreach ( $keys as $key ) {
3825 if ( isset( $values[ $key ] ) ) {
3826 $values[ $key ] = self::kses( $values[ $key ], 'all' );
3827 }
3828 }
3829
3830 return $values;
3831 }
3832
3833 /**
3834 * Some back end fields allow privleged users to add scripts.
3835 * A site that uses the DISALLOW_UNFILTERED_HTML always remove scripts on echo.
3836 *
3837 * @since 5.0.13
3838 *
3839 * @param string $value
3840 * @param array|string $allowed 'all' for everything included as defaults.
3841 * @return string
3842 */
3843 public static function maybe_kses( $value, $allowed = 'all' ) {
3844 if ( self::should_never_allow_unfiltered_html() ) {
3845 $value = self::kses( $value, $allowed );
3846 }
3847 return $value;
3848 }
3849
3850 /**
3851 * @since 5.0.16
3852 *
3853 * @return bool
3854 */
3855 public static function show_landing_pages() {
3856 return self::show_new_feature( 'landing' );
3857 }
3858
3859 /**
3860 * @since 5.0.16
3861 *
3862 * @return array
3863 */
3864 public static function get_landing_page_upgrade_data_params( $medium = 'landing' ) {
3865 $params = array(
3866 'medium' => $medium,
3867 'upgrade' => __( 'Form Landing Pages', 'formidable' ),
3868 'message' => __( 'Easily manage a landing page for your form. Upgrade to get form landing pages.', 'formidable' ),
3869 'screenshot' => 'landing.png',
3870 );
3871 return self::get_upgrade_data_params( 'landing', $params );
3872 }
3873
3874 /**
3875 * @since 5.0.17
3876 *
3877 * @param string $plugin
3878 * @return string|false
3879 */
3880 private static function get_plan_required( $plugin ) {
3881 $link = FrmAddonsController::install_link( $plugin );
3882 return FrmFormsHelper::get_plan_required( $link );
3883 }
3884
3885 /**
3886 * @since 5.0.17
3887 *
3888 * @param string $feature
3889 * @return bool
3890 */
3891 public static function show_new_feature( $feature ) {
3892 $link = FrmAddonsController::install_link( $feature );
3893 return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );
3894 }
3895
3896 /**
3897 * @since 5.0.17
3898 *
3899 * @param string $plugin
3900 * @param array $params
3901 * @return array
3902 */
3903 public static function get_upgrade_data_params( $plugin, $params ) {
3904 $link = FrmAddonsController::install_link( $plugin );
3905 if ( ! $link ) {
3906 return $params;
3907 }
3908
3909 if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
3910 $params['oneclick'] = json_encode( $link );
3911 unset( $params['message'] );
3912 if ( ! isset( $params['medium'] ) ) {
3913 $params['medium'] = $plugin;
3914 }
3915 } else {
3916 $params['requires'] = FrmFormsHelper::get_plan_required( $link );
3917 }
3918
3919 return $params;
3920 }
3921
3922 /**
3923 * Returns true if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f], false otherwise.
3924 * Not every server installs the ctype extension, so use a fallback if the function does not exist.
3925 *
3926 * @since 5.0.17
3927 *
3928 * @param string $text
3929 * @return bool
3930 */
3931 public static function ctype_xdigit( $text ) {
3932 if ( function_exists( 'ctype_xdigit' ) ) {
3933 return ctype_xdigit( $text );
3934 }
3935 return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Fa-f0-9]/', $text );
3936 }
3937
3938 /**
3939 * Set the current screen to avoid undefined notices.
3940 *
3941 * @since 5.2.01
3942 */
3943 public static function set_current_screen_and_hook_suffix() {
3944 global $hook_suffix;
3945 if ( is_null( $hook_suffix ) ) {
3946 // $hook_suffix gets used in substr so make sure it's not null. PHP 8.1 deprecates null in substr.
3947 $hook_suffix = ''; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
3948 }
3949 set_current_screen();
3950 }
3951
3952 /**
3953 * Shows pill text.
3954 *
3955 * @since 5.2.02
3956 *
3957 * @param string $text Text in the pill. Default is NEW.
3958 */
3959 public static function show_pill_text( $text = null ) {
3960 if ( null === $text ) {
3961 $text = __( 'NEW', 'formidable' );
3962 }
3963 echo '<span class="frm-meta-tag frm-new-pill">' . esc_html( $text ) . '</span>';
3964 }
3965
3966 /**
3967 * Count the number of decimals digits.
3968 *
3969 * @since 5.2.07
3970 *
3971 * @param mixed $num Number.
3972 * @return int|false Returns `false` if the passed parameter is not number.
3973 */
3974 public static function count_decimals( $num ) {
3975 if ( ! is_numeric( $num ) ) {
3976 return false;
3977 }
3978
3979 $num = (string) $num;
3980 $parts = explode( '.', $num );
3981 if ( 1 === count( $parts ) ) {
3982 return 0;
3983 }
3984
3985 return strlen( $parts[ count( $parts ) - 1 ] );
3986 }
3987
3988 /**
3989 * Prevent a fatal error in PHP8 if gmt_offset happens to be set an empty string.
3990 * This is a bug in WordPress. It isn't safe to call current_time( 'timestamp' ) without this with an empty string offset.
3991 * In the future this might be safe to remove. Keep an eye on the current_time function in functions.php.
3992 *
3993 * @since 5.3.1
3994 *
3995 * @return void
3996 */
3997 public static function filter_gmt_offset() {
3998 if ( self::$added_gmt_offset_filter ) {
3999 // Avoid adding twice.
4000 return;
4001 }
4002
4003 add_filter(
4004 'option_gmt_offset',
4005 function ( $offset ) {
4006 if ( ! is_string( $offset ) || is_numeric( $offset ) ) {
4007 // Leave a valid value alone.
4008 return $offset;
4009 }
4010
4011 return 0;
4012 }
4013 );
4014 self::$added_gmt_offset_filter = true;
4015 }
4016
4017 /**
4018 * @since 5.3.1
4019 *
4020 * @return bool
4021 */
4022 public static function on_form_listing_page() {
4023 if ( ! self::is_admin_page( 'formidable' ) ) {
4024 return false;
4025 }
4026
4027 $action = self::simple_get( 'frm_action', 'sanitize_title' );
4028 return ! $action || in_array( $action, self::get_form_listing_page_actions(), true );
4029 }
4030
4031 /**
4032 * Get all actions that also display the forms list.
4033 *
4034 * @since 5.3.1
4035 *
4036 * @return array<string>
4037 */
4038 private static function get_form_listing_page_actions() {
4039 return array( 'list', 'trash', 'untrash', 'destroy' );
4040 }
4041
4042 /**
4043 * Safely call get_plugins, importing the required files if they are not yet loaded.
4044 *
4045 * @since 5.5
4046 *
4047 * @return array
4048 */
4049 public static function get_plugins() {
4050 if ( ! function_exists( 'get_plugins' ) ) {
4051 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4052 }
4053 return get_plugins();
4054 }
4055
4056 /**
4057 * Make sure that the file we're trying to load is in fact the expected file type, and that it's coming from our S3 bucket.
4058 * This is to make sure that the URL can't be exploited for a SSRF attack.
4059 *
4060 * @since 5.5.5
4061 *
4062 * @param string $url
4063 * @param string $expected_extension
4064 * @return bool
4065 */
4066 public static function validate_url_is_in_s3_bucket( $url, $expected_extension ) {
4067 $file_is_in_expected_s3_bucket = 0 === strpos( $url, 'https://s3.amazonaws.com/fp.strategy11.com' );
4068 if ( ! $file_is_in_expected_s3_bucket ) {
4069 return false;
4070 }
4071
4072 $parsed = parse_url( $url );
4073 if ( ! is_array( $parsed ) ) {
4074 // URL is malformed.
4075 return false;
4076 }
4077
4078 $path = $parsed['path'];
4079 $ext = pathinfo( $path, PATHINFO_EXTENSION );
4080 if ( $expected_extension !== $ext ) {
4081 // The URL isn't to an XML file.
4082 return false;
4083 }
4084
4085 return true;
4086 }
4087
4088 /**
4089 * @since 4.08
4090 * @deprecated 4.09.01
4091 */
4092 public static function expiring_message() {
4093 _deprecated_function( __METHOD__, '4.09.01', 'FrmProAddonsController::expiring_message' );
4094 if ( is_callable( 'FrmProAddonsController::expiring_message' ) ) {
4095 FrmProAddonsController::expiring_message();
4096 }
4097 }
4098
4099 /**
4100 * Use the WP 4.7 wp_doing_ajax function
4101 *
4102 * @since 2.05.07
4103 * @deprecated 4.04.04
4104 */
4105 public static function wp_doing_ajax() {
4106 _deprecated_function( __METHOD__, '4.04.04', 'wp_doing_ajax' );
4107 return wp_doing_ajax();
4108 }
4109
4110 /**
4111 * @deprecated 4.0
4112 */
4113 public static function insert_opt_html( $args ) {
4114 _deprecated_function( __METHOD__, '4.0', 'FrmFormsHelper::insert_opt_html' );
4115 FrmFormsHelper::insert_opt_html( $args );
4116 }
4117
4118 /**
4119 * @deprecated 3.01
4120 * @codeCoverageIgnore
4121 */
4122 public static function sanitize_array( &$values ) {
4123 FrmDeprecated::sanitize_array( $values );
4124 }
4125
4126 /**
4127 * @since 2.0
4128 * @deprecated 5.0.13
4129 *
4130 * @return string The base Google APIS url for the current version of jQuery UI
4131 */
4132 public static function jquery_ui_base_url() {
4133 _deprecated_function( __FUNCTION__, '5.0.13', 'FrmProAppHelper::jquery_ui_base_url' );
4134 return is_callable( 'FrmProAppHelper::jquery_ui_base_url' ) ? FrmProAppHelper::jquery_ui_base_url() : '';
4135 }
4136
4137 /**
4138 * @since 4.07
4139 * @deprecated 6.0
4140 */
4141 public static function renewal_message() {
4142 _deprecated_function( __METHOD__, '6.0', 'FrmProAddonsController::renewal_message' );
4143 }
4144
4145 /**
4146 * Display a dismissable warning message and save its dismissal state.
4147 *
4148 * @since 6.3
4149 *
4150 * @param string $message The warning message to display.
4151 * @param string $option The unique identifier for the dismissal state of the message and the WP Ajax action.
4152 * @return void
4153 */
4154 public static function add_dismissable_warning_message( $message = '', $option = '' ) {
4155 if ( ! $message || ! $option ) {
4156 return;
4157 }
4158
4159 $ajax_callback = function () use ( $option ) {
4160 self::dismiss_warning_message( $option );
4161 };
4162
4163 // We're handling JS codes with `doJsonPost` and it adds 'frm_' to the beginning of the action.
4164 // To prevent any issues, we add 'frm_' from the beginning of the action.
4165 add_action( 'wp_ajax_frm_' . $option, $ajax_callback );
4166
4167 add_filter(
4168 'frm_message_list',
4169 function ( $show_messages ) use ( $message, $option ) {
4170 if ( get_option( $option, false ) ) {
4171 return $show_messages;
4172 }
4173
4174 $dismiss_icon = self::icon_by_class(
4175 'frmfont frm_close_icon',
4176 array(
4177 'aria-label' => _x( 'Dismiss', 'warning message: close icon label', 'formidable' ),
4178 'echo' => false,
4179 )
4180 );
4181
4182 $show_messages[] = $message;
4183 $show_messages[] = '<span class="frm-warning-dismiss frmsvg" data-action="' . esc_attr( $option ) . '">' . $dismiss_icon . '</span>';
4184
4185 return $show_messages;
4186 }
4187 );
4188 }
4189
4190 /**
4191 * Dismiss a warning message and update the dismissal state.
4192 *
4193 * @since 6.3
4194 *
4195 * @param string $option The unique identifier for the dismissal state of the message.
4196 * @return void
4197 */
4198 public static function dismiss_warning_message( $option = '' ) {
4199 self::permission_check( 'frm_change_settings' );
4200 check_ajax_referer( 'frm_ajax', 'nonce' );
4201
4202 if ( $option ) {
4203 update_option( $option, true, 'no' );
4204 }
4205
4206 wp_send_json_success();
4207 }
4208
4209 /**
4210 * Lite license copy.
4211 * Used in FrmDashboardController & FrmSettingsController
4212 *
4213 * @since 6.8
4214 *
4215 * @return string
4216 */
4217 public static function copy_for_lite_license() {
4218 $message = __( 'You\'re using Formidable Forms Lite - no license needed. Enjoy!', 'formidable' ) . ' 🙂';
4219
4220 if ( is_callable( 'FrmProAddonsController::get_readable_license_type' ) && ! class_exists( 'FrmProDashboardController' ) ) {
4221 // Manage PRO versions without PRO dashboard functionality.
4222 $license_type = FrmProAddonsController::get_readable_license_type();
4223 if ( 'lite' !== strtolower( $license_type ) ) {
4224 $message = 'Formidable Pro ' . $license_type;
4225 }
4226 }
4227
4228 return apply_filters( 'frm_license_type_text', $message );
4229 }
4230
4231 /**
4232 * Removes scripts that are unnecessarily loaded across the pages!
4233 *
4234 * @since 6.9
4235 */
4236 public static function dequeue_extra_global_scripts() {
4237 wp_dequeue_script( 'frm-surveys-admin' );
4238 wp_dequeue_script( 'frm-quizzes-form-action' );
4239 }
4240 }
4241