PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.10
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.10
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.10, at classes/helpers/FrmAppHelper.php

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