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

4,329 lines 119.5 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.12';
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 // Previously 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_settings
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 . '><use xlink:href="#' . esc_attr( $icon ) . '" /></svg>';
1171 }
1172
1173 if ( $echo ) {
1174 echo self::kses_icon( $icon ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1175 } else {
1176 return $icon;
1177 }
1178 }
1179
1180 /**
1181 * Run kses for icons. It needs to add a few filters first in order to preserve some custom style values.
1182 *
1183 * @since 5.0.13
1184 *
1185 * @param string $icon
1186 * @return string
1187 */
1188 public static function kses_icon( $icon ) {
1189 add_filter( 'safe_style_css', 'FrmAppHelper::allow_vars_in_styles' );
1190 add_filter( 'safecss_filter_attr_allow_css', 'FrmAppHelper::allow_style', 10, 2 );
1191 add_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_icon_tags' );
1192 $icon = self::kses( $icon, 'all' );
1193 remove_filter( 'safe_style_css', 'FrmAppHelper::allow_vars_in_styles' );
1194 remove_filter( 'safecss_filter_attr_allow_css', 'FrmAppHelper::allow_style' );
1195 remove_filter( 'frm_striphtml_allowed_tags', 'FrmAppHelper::add_allowed_icon_tags' );
1196 return $icon;
1197 }
1198
1199 /**
1200 * @since 5.0.13.1
1201 *
1202 * @param array $allowed_html
1203 * @return array
1204 */
1205 public static function add_allowed_icon_tags( $allowed_html ) {
1206 $allowed_html['svg']['data-open'] = true;
1207 $allowed_html['svg']['title'] = true;
1208 return $allowed_html;
1209 }
1210
1211 /**
1212 * @since 5.0.13
1213 *
1214 * @param array $allowed_attr
1215 * @return array
1216 */
1217 public static function allow_vars_in_styles( $allowed_attr ) {
1218 $allowed_attr[] = '--primary-700';
1219 return $allowed_attr;
1220 }
1221
1222 /**
1223 * @since 5.0.13
1224 *
1225 * @param bool $allow_css
1226 * @param string $css_string
1227 */
1228 public static function allow_style( $allow_css, $css_string ) {
1229 if ( ! $allow_css && 0 === strpos( $css_string, '--primary-700:' ) ) {
1230 $split = explode( ':', $css_string, 2 );
1231 $allow_css = 2 === count( $split ) && self::is_a_valid_color( $split[1] );
1232 }
1233 return $allow_css;
1234 }
1235
1236 /**
1237 * @since 5.0.13
1238 *
1239 * @param string $value
1240 * @return bool
1241 */
1242 private static function is_a_valid_color( $value ) {
1243 $match = 0;
1244 if ( 0 === strpos( $value, 'rgba(' ) ) {
1245 $match = preg_match( '/^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(\d*(?:\.\d+)?)\)$/', $value );
1246 } elseif ( 0 === strpos( $value, 'rgb(' ) ) {
1247 $match = preg_match( '/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/', $value );
1248 } elseif ( 0 === strpos( $value, '#' ) ) {
1249 $match = preg_match( '/^#([a-f0-9]{6}|[a-f0-9]{3})\b$/', $value );
1250 }
1251 return (bool) $match;
1252 }
1253
1254 /**
1255 * Include svg images.
1256 *
1257 * @since 4.0.02
1258 * @return void
1259 */
1260 public static function include_svg() {
1261 if ( self::$included_svg ) {
1262 return;
1263 }
1264
1265 // Use readfile instead of include_once because of a default security rule in Snuffleupagus.
1266 readfile( self::plugin_path() . '/images/icons.svg' );
1267 self::$included_svg = true;
1268 }
1269
1270 /**
1271 * Convert an associative array to HTML values.
1272 *
1273 * @since 4.0.02
1274 * @since 5.0.13 added $echo parameter.
1275 *
1276 * @param array $atts
1277 * @param bool $echo
1278 * @return string|void
1279 */
1280 public static function array_to_html_params( $atts, $echo = false ) {
1281 $callback = function () use ( $atts ) {
1282 if ( $atts ) {
1283 foreach ( $atts as $key => $value ) {
1284 echo ' ' . esc_attr( $key ) . '="' . esc_attr( $value ) . '"';
1285 }
1286 }
1287 };
1288 return self::clip( $callback, $echo );
1289 }
1290
1291 /**
1292 * Call an echo function and either echo it or return the result as a string.
1293 *
1294 * @since 5.0.13
1295 *
1296 * @param Closure $echo_function
1297 * @param bool $echo
1298 * @return string|null
1299 */
1300 public static function clip( $echo_function, $echo = false ) {
1301 if ( ! $echo ) {
1302 ob_start();
1303 }
1304
1305 if ( is_callable( $echo_function ) ) {
1306 $echo_function();
1307 }
1308
1309 if ( ! $echo ) {
1310 $return = ob_get_contents();
1311 ob_end_clean();
1312 return $return;
1313 }
1314 }
1315
1316 /**
1317 * @since 3.0
1318 *
1319 * @param array $atts
1320 * @return void
1321 */
1322 public static function get_admin_header( $atts ) {
1323 $has_nav = ! empty( $atts['form'] ) && empty( $atts['is_template'] );
1324 if ( empty( $atts['close'] ) ) {
1325 $atts['close'] = admin_url( 'admin.php?page=formidable' );
1326 }
1327 if ( ! isset( $atts['import_link'] ) ) {
1328 $atts['import_link'] = false;
1329 }
1330
1331 include self::plugin_path() . '/classes/views/shared/admin-header.php';
1332 }
1333
1334 /**
1335 * @since 6.0
1336 *
1337 * @param string $type
1338 * @return void
1339 */
1340 public static function import_link( $type = 'secondary' ) {
1341 ?>
1342 <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">
1343 <?php esc_html_e( 'Import', 'formidable' ); ?>
1344 </a>
1345 <?php
1346 }
1347
1348 /**
1349 * Print applicable admin banner.
1350 *
1351 * @since 5.4.2
1352 *
1353 * @param bool $should_show_lite_upgrade
1354 * @return void
1355 */
1356 public static function print_admin_banner( $should_show_lite_upgrade ) {
1357 if ( ! current_user_can( 'administrator' ) ) {
1358 FrmInbox::maybe_show_banner();
1359 return;
1360 }
1361
1362 if ( self::maybe_show_license_warning() || FrmInbox::maybe_show_banner() || ! $should_show_lite_upgrade || self::pro_is_installed() ) {
1363 // Print license warning or inbox banner and exit if either prints.
1364 // And exit before printing the upgrade bar if it shouldn't be shown.
1365 return;
1366 }
1367 ?>
1368 <div class="frm-upgrade-bar">
1369 <?php
1370 printf(
1371 /* translators: %1$s: Start link HTML, %2$s: End link HTML */
1372 esc_html__( 'You\'re using Formidable Forms Lite. To unlock more features consider %1$supgrading to PRO%2$s.', 'formidable' ),
1373 '<a href="' . esc_url( self::admin_upgrade_link( 'settings-license' ) ) . '">',
1374 '</a>'
1375 );
1376 ?>
1377 </div>
1378 <?php
1379 }
1380
1381 /**
1382 * @since 5.4.2
1383 *
1384 * @return bool True if a banner is available and shown.
1385 */
1386 private static function maybe_show_license_warning() {
1387 return is_callable( 'FrmProAddonsController::admin_banner' ) && FrmProAddonsController::admin_banner();
1388 }
1389
1390 /**
1391 * Render a button for a new item (Form, Application, etc).
1392 *
1393 * @since 3.0
1394 * @param array $atts {
1395 * Details about the button.
1396 *
1397 * @type array $link_hook Custom link hook, calls do_action and exits early.
1398 * @type string $new_link Href value, default #.
1399 * @type string $class Custom class names, space separated.
1400 * @type string $button_text Button text. Default "Add New".
1401 * }
1402 * @return void
1403 */
1404 public static function add_new_item_link( $atts ) {
1405 if ( isset( $atts['link_hook'] ) ) {
1406 do_action( $atts['link_hook']['hook'], $atts['link_hook']['param'] );
1407 return;
1408 }
1409
1410 if ( empty( $atts['new_link'] ) && empty( $atts['create_form'] ) && empty( $atts['class'] ) ) {
1411 // Do not render a button if none of these attributes are set.
1412 return;
1413 }
1414
1415 $href = ! empty( $atts['new_link'] ) ? esc_url( $atts['new_link'] ) : '#';
1416 $class = 'button button-primary frm-button-primary';
1417
1418 if ( ! empty( $atts['class'] ) ) {
1419 $class .= ' ' . $atts['class'];
1420 }
1421
1422 $button_text = ! empty( $atts['button_text'] ) ? $atts['button_text'] : __( 'Add New', 'formidable' );
1423
1424 require self::plugin_path() . '/classes/views/shared/add-button.php';
1425 }
1426
1427 /**
1428 * @since 3.06
1429 */
1430 public static function show_search_box( $atts ) {
1431 $defaults = array(
1432 'placeholder' => '',
1433 'tosearch' => '',
1434 'text' => __( 'Search', 'formidable' ),
1435 'input_id' => '',
1436 'value' => false,
1437 );
1438 $atts = array_merge( $defaults, $atts );
1439
1440 if ( $atts['input_id'] === 'template' && empty( $atts['tosearch'] ) ) {
1441 $atts['tosearch'] = 'frm-card';
1442 }
1443
1444 $class = 'frm-search-input';
1445 if ( ! empty( $atts['tosearch'] ) ) {
1446 $class .= ' frm-auto-search';
1447 }
1448
1449 $input_id = $atts['input_id'] . '-search-input';
1450
1451 $input_atts = array(
1452 'type' => 'search',
1453 'id' => $input_id,
1454 'name' => 's',
1455 'placeholder' => $atts['placeholder'],
1456 'class' => $class,
1457 'data-tosearch' => $atts['tosearch'],
1458 );
1459
1460 if ( is_string( $atts['value'] ) ) {
1461 $input_atts['value'] = $atts['value'];
1462 } elseif ( isset( $_REQUEST['s'] ) ) {
1463 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1464 $input_atts['value'] = wp_unslash( $_REQUEST['s'] );
1465 }
1466
1467 if ( ! empty( $atts['tosearch'] ) ) {
1468 $input_atts['autocomplete'] = 'off';
1469 }
1470 ?>
1471 <p class="frm-search">
1472 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>">
1473 <?php echo esc_html( $atts['text'] ); ?>:
1474 </label>
1475 <span class="frmfont frm_search_icon"></span>
1476 <input <?php self::array_to_html_params( $input_atts, true ); ?> />
1477 <?php
1478 if ( empty( $atts['tosearch'] ) ) {
1479 submit_button( $atts['text'], 'button-secondary', '', false, array( 'id' => 'search-submit' ) );
1480 }
1481 ?>
1482 </p>
1483 <?php
1484 }
1485
1486 /**
1487 * @param string $type
1488 * @return void
1489 */
1490 public static function trigger_hook_load( $type, $object = null ) {
1491 // Only load the form hooks once.
1492 $hooks_loaded = apply_filters( 'frm_' . $type . '_hooks_loaded', false, $object );
1493 if ( ! $hooks_loaded ) {
1494 do_action( 'frm_load_' . $type . '_hooks' );
1495 }
1496 }
1497
1498 /**
1499 * Save all front-end js scripts into a single file.
1500 * And save an additional single file of all front-end Stripe JS scripts.
1501 *
1502 * @since 3.0
1503 *
1504 * @return void
1505 */
1506 public static function save_combined_js() {
1507 $file_atts = apply_filters(
1508 'frm_js_location',
1509 array(
1510 'file_name' => 'frm.min.js',
1511 'new_file_path' => self::plugin_path() . '/js',
1512 )
1513 );
1514 $new_file = new FrmCreateFile( $file_atts );
1515
1516 $files = array(
1517 self::plugin_path() . '/js/formidable.min.js',
1518 );
1519 /**
1520 * @param array $files
1521 */
1522 $files = apply_filters( 'frm_combined_js_files', $files );
1523 $new_file->combine_files( $files );
1524
1525 // Create the minified Stripe Script.
1526 $file_atts = apply_filters(
1527 'frm_stripe_js_location',
1528 array(
1529 'file_name' => 'frmstrp.min.js',
1530 'new_file_path' => self::plugin_path() . '/js',
1531 )
1532 );
1533 $new_file = new FrmCreateFile( $file_atts );
1534 $files = array(
1535 FrmStrpLiteAppHelper::plugin_path() . 'js/frmstrp.min.js',
1536 );
1537
1538 /**
1539 * @since 6.5
1540 *
1541 * @param array $files
1542 */
1543 $files = apply_filters( 'frm_stripe_combined_js_files', $files );
1544 $new_file->combine_files( $files );
1545 }
1546
1547 /**
1548 * Check a value from a shortcode to see if true or false.
1549 * True when value is 1, true, 'true', 'yes'
1550 *
1551 * @since 1.07.10
1552 *
1553 * @param string $value The value to compare.
1554 *
1555 * @return bool
1556 */
1557 public static function is_true( $value ) {
1558 return true === $value || 1 == $value || 'true' === $value || 'yes' === $value;
1559 }
1560
1561 /**
1562 * Gets all post from a specific post type.
1563 * 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.
1564 *
1565 * @since 4.10.01 Add `$post_type` argument.
1566 *
1567 * @param string $post_type Post type to query. Default is `page`.
1568 * @return WP_Post[]
1569 */
1570 public static function get_pages( $post_type = 'page' ) {
1571 $query = array(
1572 'post_type' => $post_type,
1573 'post_status' => array( 'publish', 'private' ),
1574 'numberposts' => - 1,
1575 'orderby' => 'title',
1576 'order' => 'ASC',
1577 );
1578
1579 return get_posts( $query );
1580 }
1581
1582 /**
1583 * Gets post ids and titles for a specific post type.
1584 *
1585 * @since 5.0.09
1586 *
1587 * @param string $post_type Post type to query. Default is `page`.
1588 * @return array
1589 */
1590 public static function get_post_ids_and_titles( $post_type = 'page' ) {
1591 return FrmDb::get_results(
1592 'posts',
1593 array(
1594 'post_type' => $post_type,
1595 'post_status' => array( 'publish', 'private' ),
1596 ),
1597 'ID, post_title',
1598 array(
1599 'order_by' => 'post_title ASC',
1600 )
1601 );
1602 }
1603
1604 /**
1605 * Renders an autocomplete page selection or a regular dropdown depending on
1606 * the total page count
1607 *
1608 * @since 4.03.06
1609 * @since 4.10.01 Added `post_type` and `autocomplete_placeholder` to the arguments array.
1610 *
1611 * @param array $args Selection arguments.
1612 */
1613 public static function maybe_autocomplete_pages_options( $args ) {
1614 $args = self::preformat_selection_args( $args );
1615
1616 $pages_count = wp_count_posts( $args['post_type'] );
1617
1618 if ( $pages_count->publish <= 50 ) {
1619 self::wp_pages_dropdown( $args );
1620 return;
1621 }
1622
1623 wp_enqueue_script( 'jquery-ui-autocomplete' );
1624
1625 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1626 $title = '';
1627
1628 if ( $selected ) {
1629 $title = get_the_title( $selected );
1630 }
1631
1632 ?>
1633 <input type="text" class="frm-page-search"
1634 data-post-type="<?php echo esc_attr( $args['post_type'] ); ?>"
1635 placeholder="<?php echo esc_attr( $args['autocomplete_placeholder'] ); ?>"
1636 value="<?php echo esc_attr( $title ); ?>" />
1637 <input type="hidden" name="<?php echo esc_attr( $args['field_name'] ); ?>"
1638 class="frm_autocomplete_value_input"
1639 value="<?php echo esc_attr( $selected ); ?>" />
1640 <?php
1641 }
1642
1643 /**
1644 * @param array $args
1645 * @param string $page_id Deprecated.
1646 * @param bool $truncate Deprecated.
1647 */
1648 public static function wp_pages_dropdown( $args = array(), $page_id = '', $truncate = false ) {
1649 self::prep_page_dropdown_params( $page_id, $truncate, $args );
1650
1651 $pages = self::get_post_ids_and_titles( $args['post_type'] );
1652 $selected = self::get_post_param( $args['field_name'], $args['page_id'], 'absint' );
1653
1654 ?>
1655 <select name="<?php echo esc_attr( $args['field_name'] ); ?>" id="<?php echo esc_attr( $args['field_name'] ); ?>" class="frm-pages-dropdown">
1656 <option value=""><?php echo esc_html( $args['placeholder'] ); ?></option>
1657 <?php foreach ( $pages as $page ) { ?>
1658 <option value="<?php echo esc_attr( $page->ID ); ?>" <?php selected( $selected, $page->ID ); ?>>
1659 <?php echo esc_html( $args['truncate'] ? self::truncate( $page->post_title, $args['truncate'] ) : $page->post_title ); ?>
1660 </option>
1661 <?php } ?>
1662 </select>
1663 <?php
1664 }
1665
1666 /**
1667 * Fill in missing parameters passed to wp_pages_dropdown().
1668 * This is for reverse compatibility with switching 3 params to 1.
1669 *
1670 * @since 4.03.06
1671 */
1672 private static function prep_page_dropdown_params( $page_id, $truncate, &$args ) {
1673 if ( ! is_array( $args ) ) {
1674 $args = array(
1675 'field_name' => $args,
1676 'page_id' => $page_id,
1677 'truncate' => $truncate,
1678 );
1679 }
1680
1681 $args = self::preformat_selection_args( $args );
1682 }
1683
1684 /**
1685 * Filter to format args for page dropdown or autocomplete
1686 *
1687 * @since 4.03.06
1688 * @since 4.10.01 Added `post_type` and `autocomplete_placeholder` to the arguments array.
1689 */
1690 private static function preformat_selection_args( $args ) {
1691 $defaults = array(
1692 'truncate' => false,
1693 'placeholder' => ' ',
1694 'field_name' => '',
1695 'page_id' => '',
1696 'post_type' => 'page',
1697 'autocomplete_placeholder' => __( 'Select a Page', 'formidable' ),
1698 );
1699
1700 return array_merge( $defaults, $args );
1701 }
1702
1703 public static function post_edit_link( $post_id ) {
1704 $post = get_post( $post_id );
1705 if ( $post ) {
1706 $post_url = admin_url( 'post.php?post=' . $post_id . '&action=edit' );
1707
1708 return '<a href="' . esc_url( $post_url ) . '">' . self::truncate( $post->post_title, 50 ) . '</a>';
1709 }
1710
1711 return '';
1712 }
1713
1714 /**
1715 * Hide the WordPress menus on some pages.
1716 *
1717 * @since 4.0
1718 *
1719 * @return bool
1720 */
1721 public static function is_full_screen() {
1722 return self::is_form_builder_page() ||
1723 self::is_style_editor_page() ||
1724 self::is_full_screen_view_builder_page();
1725 }
1726
1727 /**
1728 * Check if user is on the style editor or its alternative URL.
1729 * The first URL is a submenu "Styles" in the Formidable menu /wp-admin/admin.php?page=formidable-styles.
1730 * The alternative URL is linked as a submenu "Forms" item of the Appearance menu /wp-admin/themes.php?page=formidable-styles2.
1731 *
1732 * @since 5.5.3
1733 * @since 6.0 Added the $view parameter. Previously there was only a 'edit' view.
1734 *
1735 * @param string $view Supports 'edit', 'list', and ''. If '', both 'edit' and 'list' will match.
1736 * @return bool
1737 */
1738 public static function is_style_editor_page( $view = '' ) {
1739 if ( ! self::is_admin_page( 'formidable-styles' ) && ! self::is_admin_page( 'formidable-styles2' ) ) {
1740 return false;
1741 }
1742
1743 if ( ! in_array( $view, array( 'list', 'edit' ), true ) ) {
1744 return true;
1745 }
1746
1747 $action = self::simple_get( 'frm_action' );
1748 $is_edit_mode = 'edit' === $action || ( ! $action && ! self::simple_get( 'id' ) && ! self::simple_get( 'form' ) );
1749
1750 if ( ! $is_edit_mode && class_exists( 'FrmProStylesController' ) && in_array( $action, array( 'new_style', 'duplicate' ), true ) ) {
1751 $is_edit_mode = true;
1752 }
1753
1754 $checking_for_edit_mode = 'edit' === $view;
1755
1756 return $is_edit_mode === $checking_for_edit_mode;
1757 }
1758
1759 /**
1760 * @since 5.5.3
1761 *
1762 * @return bool
1763 */
1764 private static function is_full_screen_view_builder_page() {
1765 return self::is_admin_page( 'formidable-views-editor' );
1766 }
1767
1768 /**
1769 * @since 4.0
1770 * @deprecated 5.5.3
1771 */
1772 public static function maybe_full_screen_link( $link ) {
1773 _deprecated_function( __METHOD__, '5.5.3' );
1774 return $link;
1775 }
1776
1777 /**
1778 * @param string $field_name
1779 * @param array|string $capability
1780 * @param string $multiple 'single' and 'multiple'.
1781 */
1782 public static function wp_roles_dropdown( $field_name, $capability, $multiple = 'single' ) {
1783 ?>
1784 <select name="<?php echo esc_attr( $field_name ); ?>" id="<?php echo esc_attr( $field_name ); ?>"
1785 <?php echo 'multiple' === $multiple ? 'multiple="multiple"' : ''; ?>
1786 class="frm_multiselect">
1787 <?php self::roles_options( $capability ); ?>
1788 </select>
1789 <?php
1790 }
1791
1792 /**
1793 * @since 4.07
1794 * @param array|string $selected
1795 * @param string $current
1796 */
1797 private static function selected( $selected, $current ) {
1798 if ( is_callable( 'FrmProAppHelper::selected' ) ) {
1799 FrmProAppHelper::selected( $selected, $current );
1800 } else {
1801 selected( in_array( $current, (array) $selected, true ) );
1802 }
1803 }
1804
1805 /**
1806 * @param array|string $capability
1807 */
1808 public static function roles_options( $capability ) {
1809 global $frm_vars;
1810 if ( isset( $frm_vars['editable_roles'] ) ) {
1811 $editable_roles = $frm_vars['editable_roles'];
1812 } else {
1813 $editable_roles = get_editable_roles();
1814 $frm_vars['editable_roles'] = $editable_roles;
1815 }
1816
1817 foreach ( $editable_roles as $role => $details ) {
1818 $name = translate_user_role( $details['name'] );
1819 ?>
1820 <option value="<?php echo esc_attr( $role ); ?>" <?php self::selected( $capability, $role ); ?>><?php echo esc_html( $name ); ?> </option>
1821 <?php
1822 unset( $role, $details );
1823 }
1824 }
1825
1826 /**
1827 * Gets the list of capabilities.
1828 *
1829 * @since 5.0 Parameter `$type` supports `pro_only` value.
1830 *
1831 * @param string $type Supports `auto`, `pro`, or `pro_only`.
1832 * @return array
1833 */
1834 public static function frm_capabilities( $type = 'auto' ) {
1835 if ( ! self::pro_is_installed() && ! in_array( $type, array( 'pro', 'pro_only' ), true ) ) {
1836 return self::get_lite_capabilities();
1837 }
1838
1839 $pro_cap = array(
1840 'frm_create_entries' => __( 'Add Entries from Admin Area', 'formidable' ),
1841 'frm_edit_entries' => __( 'Edit Entries from Admin Area', 'formidable' ),
1842 'frm_view_reports' => __( 'View Reports', 'formidable' ),
1843 );
1844 /**
1845 * @since 5.3.1
1846 *
1847 * @param array<string,string> $pro_cap
1848 */
1849 $pro_cap = apply_filters( 'frm_pro_capabilities', $pro_cap );
1850
1851 if ( ! array_key_exists( 'frm_edit_displays', $pro_cap ) && is_callable( 'FrmProAppHelper::views_is_installed' ) && FrmProAppHelper::views_is_installed() ) {
1852 // For backward compatibility, add the Add/Edit Views permission if Pro is not up to date.
1853 // This was added in 6.5.4. Remove this in the future.
1854 $pro_cap['frm_edit_displays'] = __( 'Add/Edit Views', 'formidable' );
1855 }
1856
1857 if ( 'pro_only' === $type ) {
1858 return $pro_cap;
1859 }
1860
1861 return self::get_lite_capabilities() + $pro_cap;
1862 }
1863
1864 /**
1865 * Get the list of lite plugin capabilities.
1866 *
1867 * @since 5.3.1
1868 *
1869 * @return array<string,string>
1870 */
1871 private static function get_lite_capabilities() {
1872 return array(
1873 'frm_view_forms' => __( 'View Forms List', 'formidable' ),
1874 'frm_edit_forms' => __( 'Add and Edit Forms', 'formidable' ),
1875 'frm_delete_forms' => __( 'Delete Forms', 'formidable' ),
1876 'frm_change_settings' => __( 'Access this Settings Page', 'formidable' ),
1877 'frm_view_entries' => __( 'View Entries from Admin Area', 'formidable' ),
1878 'frm_delete_entries' => __( 'Delete Entries from Admin Area', 'formidable' ),
1879 );
1880 }
1881
1882 /**
1883 * Call the WordPress current_user_can but also validate empty strings as true for any logged in user
1884 *
1885 * @since 4.06.03
1886 *
1887 * @param string $role
1888 *
1889 * @return bool
1890 */
1891 public static function current_user_can( $role ) {
1892 if ( $role === '-1' ) {
1893 return false;
1894 }
1895
1896 if ( $role === 'loggedout' ) {
1897 return ! is_user_logged_in();
1898 }
1899
1900 if ( $role === 'loggedin' || ! $role ) {
1901 return is_user_logged_in();
1902 }
1903
1904 if ( $role == 1 ) {
1905 $role = 'administrator';
1906 }
1907
1908 if ( ! is_user_logged_in() ) {
1909 return false;
1910 }
1911
1912 return current_user_can( $role );
1913 }
1914
1915 /**
1916 * @param array|string $needed_role
1917 * @return bool
1918 */
1919 public static function user_has_permission( $needed_role ) {
1920 if ( is_array( $needed_role ) ) {
1921 foreach ( $needed_role as $role ) {
1922 if ( self::current_user_can( $role ) ) {
1923 return true;
1924 }
1925 }
1926
1927 return false;
1928 }
1929
1930 $can = self::current_user_can( $needed_role );
1931
1932 if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ) ) ) {
1933 return $can;
1934 }
1935
1936 $roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
1937 foreach ( $roles as $role ) {
1938 if ( current_user_can( $role ) ) {
1939 return true;
1940 }
1941 if ( $role == $needed_role ) {
1942 break;
1943 }
1944 }
1945
1946 return false;
1947 }
1948
1949 /**
1950 * Make sure administrators can see Formidable menu
1951 *
1952 * @since 2.0
1953 */
1954 public static function maybe_add_permissions() {
1955 self::force_capability( 'frm_view_entries' );
1956
1957 if ( ! current_user_can( 'administrator' ) || current_user_can( 'frm_view_forms' ) ) {
1958 return;
1959 }
1960
1961 $user_id = get_current_user_id();
1962 $user = new WP_User( $user_id );
1963 $frm_roles = self::frm_capabilities();
1964 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1965 $user->add_cap( $frm_role );
1966 unset( $frm_role, $frm_role_description );
1967 }
1968 }
1969
1970 /**
1971 * Make sure admins have permission to see the menu items
1972 *
1973 * @since 2.0.6
1974 *
1975 * @param string $cap
1976 * @return void
1977 */
1978 public static function force_capability( $cap = 'frm_change_settings' ) {
1979 if ( current_user_can( 'administrator' ) && ! current_user_can( $cap ) ) {
1980 $role = get_role( 'administrator' );
1981 $frm_roles = self::frm_capabilities();
1982 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1983 $role->add_cap( $frm_role );
1984 }
1985 }
1986 }
1987
1988 /**
1989 * Check if the user has permission for action.
1990 * Return permission message and stop the action if no permission
1991 *
1992 * @since 2.0
1993 *
1994 * @param string $permission
1995 */
1996 public static function permission_check( $permission, $show_message = 'show' ) {
1997 $permission_error = self::permission_nonce_error( $permission );
1998 if ( $permission_error !== false ) {
1999 if ( 'hide' == $show_message ) {
2000 $permission_error = '';
2001 }
2002 wp_die( esc_html( $permission_error ) );
2003 }
2004 }
2005
2006 /**
2007 * Check user permission and nonce
2008 *
2009 * @since 2.0
2010 *
2011 * @param string $permission
2012 *
2013 * @return false|string The permission message or false if allowed
2014 */
2015 public static function permission_nonce_error( $permission, $nonce_name = '', $nonce = '' ) {
2016 if ( ! empty( $permission ) && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
2017 $frm_settings = self::get_settings();
2018
2019 return $frm_settings->admin_permission;
2020 }
2021
2022 $error = false;
2023 if ( empty( $nonce_name ) ) {
2024 return $error;
2025 }
2026
2027 $nonce_value = $_REQUEST && isset( $_REQUEST[ $nonce_name ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_name ] ) ) : '';
2028 if ( $_REQUEST && ( ! isset( $_REQUEST[ $nonce_name ] ) || ! wp_verify_nonce( $nonce_value, $nonce ) ) ) {
2029 $frm_settings = self::get_settings();
2030 $error = $frm_settings->admin_permission;
2031 }
2032
2033 return $error;
2034 }
2035
2036 public static function checked( $values, $current ) {
2037 if ( self::check_selected( $values, $current ) ) {
2038 echo ' checked="checked"';
2039 }
2040 }
2041
2042 public static function check_selected( $values, $current ) {
2043 $values = self::recursive_function_map( $values, 'trim' );
2044 $values = self::recursive_function_map( $values, 'htmlspecialchars_decode' );
2045
2046 $current = is_null( $current ) ? '' : htmlspecialchars_decode( trim( $current ) );
2047
2048 return ( is_array( $values ) && in_array( $current, $values ) ) || ( ! is_array( $values ) && $values == $current );
2049 }
2050
2051 public static function recursive_function_map( $value, $function ) {
2052 if ( is_array( $value ) ) {
2053 $original_function = $function;
2054 if ( count( $value ) ) {
2055 $function = explode( ', ', FrmDb::prepare_array_values( $value, $function ) );
2056 } else {
2057 $function = array( $function );
2058 }
2059 if ( ! self::is_assoc( $value ) ) {
2060 $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
2061 } else {
2062 foreach ( $value as $k => $v ) {
2063 if ( ! is_array( $v ) ) {
2064 $value[ $k ] = call_user_func( $original_function, $v );
2065 }
2066 }
2067 }
2068 } else {
2069 $value = self::maybe_update_value_if_null( $value, $function );
2070 $value = call_user_func( $function, $value );
2071 }
2072
2073 return $value;
2074 }
2075
2076 /**
2077 * Updates value to empty string if it is null and being passed to a string function.
2078 *
2079 * @since 6.8.4
2080 * @param mixed $value
2081 * @param string $function
2082 * @return mixed
2083 */
2084 private static function maybe_update_value_if_null( $value, $function ) {
2085 if ( null === $value && in_array( $function, array( 'trim', 'strlen' ), true ) ) {
2086 $value = '';
2087 }
2088
2089 return $value;
2090 }
2091
2092 public static function is_assoc( $array ) {
2093 return (bool) count( array_filter( array_keys( $array ), 'is_string' ) );
2094 }
2095
2096 /**
2097 * Flatten a multi-dimensional array
2098 */
2099 public static function array_flatten( $array, $keys = 'keep' ) {
2100 $return = array();
2101 foreach ( $array as $key => $value ) {
2102 if ( is_array( $value ) ) {
2103 $return = array_merge( $return, self::array_flatten( $value, $keys ) );
2104 } elseif ( $keys === 'keep' ) {
2105 $return[ $key ] = $value;
2106 } else {
2107 $return[] = $value;
2108 }
2109 }
2110
2111 return $return;
2112 }
2113
2114 /**
2115 * @param string $text
2116 * @param bool $is_rich_text
2117 * @return string
2118 */
2119 public static function esc_textarea( $text, $is_rich_text = false ) {
2120 $safe_text = str_replace( '&quot;', '"', $text );
2121 if ( ! $is_rich_text ) {
2122 $safe_text = htmlspecialchars( $safe_text, ENT_NOQUOTES );
2123 }
2124 $safe_text = str_replace( '&amp; ', '& ', $safe_text );
2125
2126 /**
2127 * @param string $safe_text
2128 * @param string $text
2129 */
2130 return (string) apply_filters( 'esc_textarea', $safe_text, $text );
2131 }
2132
2133 /**
2134 * Add auto paragraphs to text areas
2135 *
2136 * @since 2.0
2137 */
2138 public static function use_wpautop( $content ) {
2139 if ( apply_filters( 'frm_use_wpautop', true ) && ! is_array( $content ) ) {
2140 $content = wpautop( str_replace( '<br>', '<br />', $content ) );
2141 }
2142
2143 return $content;
2144 }
2145
2146 public static function replace_quotes( $val ) {
2147 // Replace double quotes.
2148 $val = str_replace( array( '&#8220;', '&#8221;', '&#8243;' ), '"', $val );
2149
2150 // Replace single quotes.
2151 $val = str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
2152
2153 return $val;
2154 }
2155
2156 /**
2157 * @param string $handle
2158 */
2159 public static function script_version( $handle, $default = 0 ) {
2160 global $wp_scripts;
2161 if ( ! $wp_scripts ) {
2162 return $default;
2163 }
2164
2165 $ver = $default;
2166 if ( ! isset( $wp_scripts->registered[ $handle ] ) ) {
2167 return $ver;
2168 }
2169
2170 $query = $wp_scripts->registered[ $handle ];
2171 if ( is_object( $query ) && ! empty( $query->ver ) ) {
2172 $ver = $query->ver;
2173 }
2174
2175 return $ver;
2176 }
2177
2178 /**
2179 * @since 5.0.13 added $echo param.
2180 *
2181 * @param string $url
2182 * @param bool $echo
2183 * @return string|null
2184 */
2185 public static function js_redirect( $url, $echo = false ) {
2186 $callback = function () use ( $url ) {
2187 echo '<script type="text/javascript">window.location="' . esc_url_raw( $url ) . '"</script>';
2188 };
2189 return self::clip( $callback, $echo );
2190 }
2191
2192 public static function get_user_id_param( $user_id ) {
2193 if ( ! $user_id || is_numeric( $user_id ) ) {
2194 return $user_id;
2195 }
2196
2197 $user_id = sanitize_text_field( $user_id );
2198 if ( $user_id === 'current' ) {
2199 $user_id = get_current_user_id();
2200 } else {
2201 if ( is_email( $user_id ) ) {
2202 $user = get_user_by( 'email', $user_id );
2203 } else {
2204 $user = get_user_by( 'login', $user_id );
2205 }
2206
2207 if ( $user ) {
2208 $user_id = $user->ID;
2209 }
2210 unset( $user );
2211 }
2212
2213 return $user_id;
2214 }
2215
2216 /**
2217 * @param string $filename
2218 * @param array $atts
2219 * @return false|string
2220 */
2221 public static function get_file_contents( $filename, $atts = array() ) {
2222 if ( ! is_file( $filename ) ) {
2223 return false;
2224 }
2225
2226 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
2227 ob_start();
2228 include $filename;
2229 $contents = ob_get_contents();
2230 ob_end_clean();
2231
2232 return $contents;
2233 }
2234
2235 /**
2236 * @param string $name
2237 * @param string $table_name
2238 * @param string $column
2239 * @param int $id
2240 * @param int $num_chars
2241 */
2242 public static function get_unique_key( $name, $table_name, $column, $id = 0, $num_chars = 5 ) {
2243 $key = '';
2244 if ( $name ) {
2245 $key = sanitize_key( $name );
2246 $key = self::maybe_clear_long_key( $key, $column );
2247 }
2248
2249 if ( ! $key ) {
2250 $key = self::generate_new_key( $num_chars );
2251 }
2252
2253 $key = self::prevent_numeric_and_reserved_keys( $key );
2254
2255 $similar_keys = FrmDb::get_col(
2256 $table_name,
2257 array(
2258 $column . ' like%' => $key,
2259 'ID !' => $id,
2260 ),
2261 $column
2262 );
2263
2264 // Create a unique field id if it has already been used.
2265 if ( in_array( $key, $similar_keys, true ) ) {
2266 $key = self::maybe_truncate_key_before_appending( $column, $key );
2267
2268 /**
2269 * Allow for a custom separator between the attempted key and the generated suffix.
2270 *
2271 * @since 5.2.03
2272 *
2273 * @param string $separator. Default empty.
2274 * @param string $key the key without the added suffix.
2275 */
2276 $separator = apply_filters( 'frm_unique_' . $column . '_separator', '', $key );
2277
2278 $suffix = 2;
2279 do {
2280 $key_check = $key . $separator . $suffix;
2281 ++$suffix;
2282 } while ( in_array( $key_check, $similar_keys, true ) );
2283
2284 $key = $key_check;
2285 }//end if
2286
2287 return $key;
2288 }
2289
2290 /**
2291 * Avoid trying to append to a really long key,
2292 * The database limit is 100 for form and field keys so we want to avoid getting too close.
2293 *
2294 * @param string $column
2295 * @param string $key
2296 * @return string
2297 */
2298 private static function maybe_truncate_key_before_appending( $column, $key ) {
2299 if ( in_array( $column, array( 'form_key', 'field_key' ), true ) ) {
2300 $max_key_length_before_truncating = 60;
2301 if ( strlen( $key ) > $max_key_length_before_truncating ) {
2302 $key = substr( $key, 0, $max_key_length_before_truncating );
2303 if ( is_numeric( $key ) ) {
2304 $key .= 'a';
2305 }
2306 }
2307 }
2308 return $key;
2309 }
2310
2311 /**
2312 * Possibly reset a key to avoid conflicts with column size limits.
2313 *
2314 * @param string $key
2315 * @param string $column
2316 * @return string either the original key value, or an empty string if the key was too long.
2317 */
2318 private static function maybe_clear_long_key( $key, $column ) {
2319 if ( 'field_key' === $column && strlen( $key ) >= 70 ) {
2320 $key = '';
2321 }
2322 return $key;
2323 }
2324
2325 /**
2326 * @param int $num_chars
2327 * @return string
2328 */
2329 private static function generate_new_key( $num_chars ) {
2330 $max_slug_value = pow( 36, $num_chars );
2331
2332 // We want to have at least 2 characters in the slug.
2333 $min_slug_value = 37;
2334 return base_convert( rand( $min_slug_value, $max_slug_value ), 10, 36 );
2335 }
2336
2337 /**
2338 * @param string $key
2339 * @return string
2340 */
2341 private static function prevent_numeric_and_reserved_keys( $key ) {
2342 if ( is_numeric( $key ) ) {
2343 $key .= 'a';
2344 } else {
2345 $not_allowed = array(
2346 'id',
2347 'key',
2348 'created-at',
2349 'detaillink',
2350 'editlink',
2351 'siteurl',
2352 'evenodd',
2353 );
2354 if ( in_array( $key, $not_allowed, true ) ) {
2355 $key .= 'a';
2356 }
2357 }
2358 return $key;
2359 }
2360
2361 /**
2362 * Editing a Form or Entry
2363 *
2364 * @param object $record
2365 * @param string $table
2366 * @param array|string $fields
2367 * @param bool $default
2368 * @param array $post_values
2369 * @param array $args
2370 *
2371 * @return array|bool
2372 */
2373 public static function setup_edit_vars( $record, $table, $fields = '', $default = false, $post_values = array(), $args = array() ) {
2374 if ( ! $record ) {
2375 return false;
2376 }
2377
2378 if ( empty( $post_values ) ) {
2379 $post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
2380 }
2381
2382 $values = array(
2383 'id' => $record->id,
2384 'fields' => array(),
2385 );
2386
2387 foreach ( array( 'name', 'description' ) as $var ) {
2388 $default_val = isset( $record->{$var} ) ? $record->{$var} : '';
2389 $values[ $var ] = self::get_param( $var, $default_val, 'get', 'wp_kses_post' );
2390 unset( $var, $default_val );
2391 }
2392
2393 $values['description'] = self::use_wpautop( $values['description'] );
2394
2395 self::fill_form_opts( $record, $table, $post_values, $values );
2396
2397 self::prepare_field_arrays( $fields, $record, $values, array_merge( $args, compact( 'default', 'post_values' ) ) );
2398
2399 if ( $table === 'entries' ) {
2400 $values = FrmEntriesHelper::setup_edit_vars( $values, $record );
2401 } elseif ( $table === 'forms' ) {
2402 $values = FrmFormsHelper::setup_edit_vars( $values, $record, $post_values );
2403 }
2404
2405 return $values;
2406 }
2407
2408 private static function prepare_field_arrays( $fields, $record, array &$values, $args ) {
2409 if ( ! empty( $fields ) ) {
2410 foreach ( (array) $fields as $field ) {
2411 if ( ! self::is_admin_page() ) {
2412 // Don't prep default values on the form settings page.
2413 $field->default_value = apply_filters( 'frm_get_default_value', $field->default_value, $field, true );
2414 }
2415 $args['parent_form_id'] = isset( $args['parent_form_id'] ) ? $args['parent_form_id'] : $field->form_id;
2416 self::fill_field_defaults( $field, $record, $values, $args );
2417 }
2418 }
2419 }
2420
2421 private static function fill_field_defaults( $field, $record, array &$values, $args ) {
2422 $post_values = $args['post_values'];
2423
2424 if ( $args['default'] ) {
2425 $meta_value = $field->default_value;
2426 } elseif ( $record->post_id && self::pro_is_installed() && isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] ) {
2427 if ( ! isset( $field->field_options['custom_field'] ) ) {
2428 $field->field_options['custom_field'] = '';
2429 }
2430 $meta_value = FrmProEntryMetaHelper::get_post_value(
2431 $record->post_id,
2432 $field->field_options['post_field'],
2433 $field->field_options['custom_field'],
2434 array(
2435 'truncate' => false,
2436 'type' => $field->type,
2437 'form_id' => $field->form_id,
2438 'field' => $field,
2439 )
2440 );
2441 } else {
2442 $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2443 }//end if
2444
2445 $field_type = isset( $post_values['field_options'][ 'type_' . $field->id ] ) ? $post_values['field_options'][ 'type_' . $field->id ] : $field->type;
2446 if ( isset( $post_values['item_meta'][ $field->id ] ) ) {
2447 $new_value = $post_values['item_meta'][ $field->id ];
2448 self::unserialize_or_decode( $new_value );
2449 } else {
2450 $new_value = $meta_value;
2451 }
2452
2453 $field_array = self::start_field_array( $field );
2454 $field_array['value'] = $new_value;
2455 $field_array['type'] = apply_filters( 'frm_field_type', $field_type, $field, $new_value );
2456 $field_array['parent_form_id'] = $args['parent_form_id'];
2457
2458 $args['field_type'] = $field_type;
2459
2460 FrmFieldsHelper::prepare_edit_front_field( $field_array, $field, $values['id'], $args );
2461
2462 if ( ! isset( $field_array['unique'] ) || ! $field_array['unique'] ) {
2463 $field_array['unique_msg'] = '';
2464 }
2465
2466 $field_array = array_merge( (array) $field->field_options, $field_array );
2467
2468 $values['fields'][ $field->id ] = $field_array;
2469 }
2470
2471 /**
2472 * @since 3.0
2473 *
2474 * @param object $field
2475 *
2476 * @return array
2477 */
2478 public static function start_field_array( $field ) {
2479 return array(
2480 'id' => $field->id,
2481 'default_value' => $field->default_value,
2482 'name' => $field->name,
2483 'description' => $field->description,
2484 'options' => $field->options,
2485 'required' => $field->required,
2486 'field_key' => $field->field_key,
2487 'field_order' => $field->field_order,
2488 'form_id' => $field->form_id,
2489 );
2490 }
2491
2492 /**
2493 * @param object $record
2494 * @param string $table
2495 * @param array $post_values
2496 * @param array $values
2497 */
2498 private static function fill_form_opts( $record, $table, $post_values, array &$values ) {
2499 if ( $table === 'entries' ) {
2500 $form = $record->form_id;
2501 FrmForm::maybe_get_form( $form );
2502 } else {
2503 $form = $record;
2504 }
2505
2506 if ( ! $form ) {
2507 return;
2508 }
2509
2510 $values['form_name'] = isset( $record->form_id ) ? $form->name : '';
2511 $values['parent_form_id'] = isset( $record->form_id ) ? $form->parent_form_id : 0;
2512
2513 if ( ! is_array( $form->options ) ) {
2514 return;
2515 }
2516
2517 foreach ( $form->options as $opt => $value ) {
2518 if ( isset( $post_values[ $opt ] ) ) {
2519 $values[ $opt ] = $post_values[ $opt ];
2520 self::unserialize_or_decode( $values[ $opt ] );
2521 } else {
2522 $values[ $opt ] = $value;
2523 }
2524 }
2525
2526 self::fill_form_defaults( $post_values, $values );
2527 }
2528
2529 /**
2530 * Set to POST value or default
2531 */
2532 private static function fill_form_defaults( $post_values, array &$values ) {
2533 $form_defaults = FrmFormsHelper::get_default_opts();
2534
2535 foreach ( $form_defaults as $opt => $default ) {
2536 if ( ! isset( $values[ $opt ] ) || $values[ $opt ] == '' ) {
2537 $values[ $opt ] = $post_values && isset( $post_values['options'][ $opt ] ) ? $post_values['options'][ $opt ] : $default;
2538 }
2539
2540 unset( $opt, $default );
2541 }
2542
2543 if ( ! isset( $values['custom_style'] ) ) {
2544 $values['custom_style'] = self::custom_style_value( $post_values );
2545 }
2546
2547 foreach ( array( 'before', 'after', 'submit' ) as $h ) {
2548 if ( ! isset( $values[ $h . '_html' ] ) ) {
2549 $values[ $h . '_html' ] = ( isset( $post_values['options'][ $h . '_html' ] ) ? $post_values['options'][ $h . '_html' ] : FrmFormsHelper::get_default_html( $h ) );
2550 }
2551 unset( $h );
2552 }
2553 }
2554
2555 /**
2556 * @since 2.2.10
2557 *
2558 * @param array $post_values
2559 *
2560 * @return bool|int
2561 */
2562 public static function custom_style_value( $post_values ) {
2563 if ( ! empty( $post_values ) && isset( $post_values['options']['custom_style'] ) ) {
2564 $custom_style = absint( $post_values['options']['custom_style'] );
2565 } else {
2566 $frm_settings = self::get_settings();
2567 $custom_style = ( $frm_settings->load_style !== 'none' );
2568 }
2569
2570 return $custom_style;
2571 }
2572
2573 /**
2574 * @param mixed $original_string
2575 * @param int|string $length
2576 * @param int $minword
2577 * @param string $continue
2578 * @return string
2579 */
2580 public static function truncate( $original_string, $length, $minword = 3, $continue = '...' ) {
2581 if ( ! is_string( $original_string ) && ! is_int( $original_string ) ) {
2582 return '';
2583 }
2584
2585 $length = (int) $length;
2586 $str = wp_strip_all_tags( (string) $original_string );
2587 $original_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $str ) );
2588
2589 if ( $length == 0 ) {
2590 return '';
2591 }
2592
2593 if ( $length <= 10 ) {
2594 $sub = self::mb_function( array( 'mb_substr', 'substr' ), array( $str, 0, $length ) );
2595 return $sub . ( $length < $original_len ? $continue : '' );
2596 }
2597
2598 $sub = '';
2599 $len = 0;
2600
2601 $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
2602
2603 if ( ! is_array( $words ) ) {
2604 return $original_string;
2605 }
2606
2607 foreach ( $words as $word ) {
2608 $part = ( $sub != '' ? ' ' : '' ) . $word;
2609 $total_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub . $part ) );
2610 if ( $total_len > $length && substr_count( $sub, ' ' ) ) {
2611 break;
2612 }
2613
2614 $sub .= $part;
2615 $len += self::mb_function( array( 'mb_strlen', 'strlen' ), array( $part ) );
2616
2617 if ( substr_count( $sub, ' ' ) > $minword && $total_len >= $length ) {
2618 break;
2619 }
2620
2621 unset( $total_len, $word );
2622 }
2623
2624 $sub = self::maybe_force_truncate_on_string_with_no_spaces( $sub, $length );
2625
2626 return $sub . ( $len < $original_len ? $continue : '' );
2627 }
2628
2629 /**
2630 * If the string is still too long because there may not have been any spaces, force truncate.
2631 *
2632 * @since 6.5.4
2633 *
2634 * @param string $sub Current substring.
2635 * @param int $length The length limit.
2636 * @return string
2637 */
2638 private static function maybe_force_truncate_on_string_with_no_spaces( $sub, $length ) {
2639 if ( strlen( $sub ) < $length + 50 ) {
2640 // If the string isn't way over the limit, leave it.
2641 return $sub;
2642 }
2643
2644 $first_space = strpos( $sub, ' ', $length );
2645 if ( false !== $first_space ) {
2646 // Ignore anything with spaces.
2647 return $sub;
2648 }
2649
2650 return substr( $sub, 0, $length + 10 );
2651 }
2652
2653 public static function mb_function( $function_names, $args ) {
2654 $mb_function_name = $function_names[0];
2655 $function_name = $function_names[1];
2656 if ( function_exists( $mb_function_name ) ) {
2657 $function_name = $mb_function_name;
2658 }
2659
2660 return call_user_func_array( $function_name, $args );
2661 }
2662
2663 public static function get_formatted_time( $date, $date_format = '', $time_format = '' ) {
2664 if ( empty( $date ) ) {
2665 return $date;
2666 }
2667
2668 if ( empty( $date_format ) ) {
2669 $date_format = get_option( 'date_format' );
2670 }
2671
2672 if ( preg_match( '/^\d{1-2}\/\d{1-2}\/\d{4}$/', $date ) && self::pro_is_installed() ) {
2673 $frmpro_settings = new FrmProSettings();
2674 $date = FrmProAppHelper::convert_date( $date, $frmpro_settings->date_format, 'Y-m-d' );
2675 }
2676
2677 $formatted = self::get_localized_date( $date_format, $date );
2678
2679 $do_time = ( gmdate( 'H:i:s', strtotime( $date ) ) != '00:00:00' );
2680 if ( $do_time ) {
2681 $formatted .= self::add_time_to_date( $time_format, $date );
2682 }
2683
2684 return $formatted;
2685 }
2686
2687 private static function add_time_to_date( $time_format, $date ) {
2688 if ( empty( $time_format ) ) {
2689 $time_format = get_option( 'time_format' );
2690 }
2691
2692 $trimmed_format = trim( $time_format );
2693 $time = '';
2694 if ( $time_format && ! empty( $trimmed_format ) ) {
2695 $time = ' ' . __( 'at', 'formidable' ) . ' ' . self::get_localized_date( $time_format, $date );
2696 }
2697
2698 return $time;
2699 }
2700
2701 /**
2702 * @since 2.0.8
2703 */
2704 public static function get_localized_date( $date_format, $date ) {
2705 $date = get_date_from_gmt( $date );
2706
2707 return date_i18n( $date_format, strtotime( $date ) );
2708 }
2709
2710 /**
2711 * Gets the time ago in words.
2712 *
2713 * @param int $from In seconds.
2714 * @param int|string $to In seconds.
2715 *
2716 * @return string $time_ago
2717 */
2718 public static function human_time_diff( $from, $to = '', $levels = 1 ) {
2719 if ( empty( $to ) && 0 !== $to ) {
2720 $now = new DateTime();
2721 } else {
2722 $now = new DateTime( '@' . $to );
2723 }
2724 $ago = new DateTime( '@' . $from );
2725
2726 // Get the time difference
2727 $diff_object = $now->diff( $ago );
2728 $diff = get_object_vars( $diff_object );
2729
2730 // Add week amount and update day amount
2731 $diff['w'] = floor( $diff['d'] / 7 );
2732 $diff['d'] -= $diff['w'] * 7;
2733
2734 $time_strings = self::get_time_strings();
2735
2736 if ( ! is_numeric( $levels ) ) {
2737 // Show time in specified unit.
2738 $levels = self::get_unit( $levels );
2739 if ( isset( $time_strings[ $levels ] ) ) {
2740 $diff = array(
2741 $levels => self::time_format( $levels, $diff ),
2742 );
2743 $time_strings = array(
2744 $levels => $time_strings[ $levels ],
2745 );
2746 }
2747 $levels = 1;
2748 }
2749
2750 foreach ( $time_strings as $k => $v ) {
2751 if ( isset( $diff[ $k ] ) && $diff[ $k ] ) {
2752 $time_strings[ $k ] = $diff[ $k ] . ' ' . ( $diff[ $k ] > 1 ? $v[1] : $v[0] );
2753 } elseif ( isset( $diff[ $k ] ) && count( $time_strings ) === 1 ) {
2754 // Account for 0.
2755 $time_strings[ $k ] = $diff[ $k ] . ' ' . $v[1];
2756 } else {
2757 unset( $time_strings[ $k ] );
2758 }
2759 }
2760
2761 $levels_deep = apply_filters( 'frm_time_ago_levels', $levels, compact( 'time_strings', 'from', 'to' ) );
2762 $time_strings = array_slice( $time_strings, 0, absint( $levels_deep ) );
2763 $time_ago_string = implode( ' ', $time_strings );
2764
2765 return $time_ago_string;
2766 }
2767
2768 /**
2769 * @since 4.05.01
2770 */
2771 private static function time_format( $unit, $diff ) {
2772 $return = array(
2773 'y' => 'y',
2774 'd' => 'days',
2775 );
2776 if ( isset( $return[ $unit ] ) ) {
2777 return $diff[ $return[ $unit ] ];
2778 }
2779
2780 $total = $diff['days'] * self::convert_time( 'd', $unit );
2781
2782 $times = array( 'h', 'i', 's' );
2783
2784 foreach ( $times as $time ) {
2785 if ( ! isset( $diff[ $time ] ) ) {
2786 continue;
2787 }
2788
2789 $total += $diff[ $time ] * self::convert_time( $time, $unit );
2790 }
2791
2792 return floor( $total );
2793 }
2794
2795 /**
2796 * @since 4.05.01
2797 */
2798 private static function convert_time( $from, $to ) {
2799 $convert = array(
2800 's' => 1,
2801 'i' => MINUTE_IN_SECONDS,
2802 'h' => HOUR_IN_SECONDS,
2803 'd' => DAY_IN_SECONDS,
2804 'w' => WEEK_IN_SECONDS,
2805 'm' => DAY_IN_SECONDS * 30.42,
2806 'y' => DAY_IN_SECONDS * 365.25,
2807 );
2808
2809 return $convert[ $from ] / $convert[ $to ];
2810 }
2811
2812 /**
2813 * @since 4.05.01
2814 */
2815 private static function get_unit( $unit ) {
2816 $units = self::get_time_strings();
2817 if ( isset( $units[ $unit ] ) || is_numeric( $unit ) ) {
2818 return $unit;
2819 }
2820
2821 foreach ( $units as $u => $strings ) {
2822 if ( in_array( $unit, $strings ) ) {
2823 return $u;
2824 }
2825 }
2826 return 1;
2827 }
2828
2829 /**
2830 * Get the translatable time strings. The untranslated version is a failsafe
2831 * in case languages are changing for the unit set in the shortcode.
2832 *
2833 * @since 2.0.20
2834 * @return array
2835 */
2836 private static function get_time_strings() {
2837 return array(
2838 'y' => array(
2839 __( 'year', 'formidable' ),
2840 __( 'years', 'formidable' ),
2841 'year',
2842 ),
2843 'm' => array(
2844 __( 'month', 'formidable' ),
2845 __( 'months', 'formidable' ),
2846 'month',
2847 ),
2848 'w' => array(
2849 __( 'week', 'formidable' ),
2850 __( 'weeks', 'formidable' ),
2851 'week',
2852 ),
2853 'd' => array(
2854 __( 'day', 'formidable' ),
2855 __( 'days', 'formidable' ),
2856 'day',
2857 ),
2858 'h' => array(
2859 __( 'hour', 'formidable' ),
2860 __( 'hours', 'formidable' ),
2861 'hour',
2862 ),
2863 'i' => array(
2864 __( 'minute', 'formidable' ),
2865 __( 'minutes', 'formidable' ),
2866 'minute',
2867 ),
2868 's' => array(
2869 __( 'second', 'formidable' ),
2870 __( 'seconds', 'formidable' ),
2871 'second',
2872 ),
2873 );
2874 }
2875
2876 // Pagination Methods.
2877
2878 /**
2879 * @param int $r_count
2880 * @param int $current_p
2881 * @param int $p_size
2882 * @return int
2883 */
2884 public static function get_last_record_num( $r_count, $current_p, $p_size ) {
2885 return ( $r_count < $current_p * $p_size ? $r_count : $current_p * $p_size );
2886 }
2887
2888 /**
2889 * @param int $r_count
2890 * @param int $current_p
2891 * @param int $p_size
2892 * @return int
2893 */
2894 public static function get_first_record_num( $r_count, $current_p, $p_size ) {
2895 if ( $current_p == 1 ) {
2896 return 1;
2897 }
2898 return self::get_last_record_num( $r_count, $current_p - 1, $p_size ) + 1;
2899 }
2900
2901 /**
2902 * @return array
2903 */
2904 public static function json_to_array( $json_vars ) {
2905 $vars = array();
2906 foreach ( $json_vars as $jv ) {
2907 $jv_name = explode( '[', $jv['name'] );
2908 $last = count( $jv_name ) - 1;
2909 foreach ( $jv_name as $p => $n ) {
2910 $name = trim( $n, ']' );
2911 if ( ! isset( $l1 ) ) {
2912 $l1 = $name;
2913 }
2914
2915 if ( ! isset( $l2 ) ) {
2916 $l2 = $name;
2917 }
2918
2919 if ( ! isset( $l3 ) ) {
2920 $l3 = $name;
2921 }
2922
2923 $this_val = $p == $last ? $jv['value'] : array();
2924
2925 switch ( $p ) {
2926 case 0:
2927 $l1 = $name;
2928 self::add_value_to_array( $name, $l1, $this_val, $vars );
2929 break;
2930
2931 case 1:
2932 $l2 = $name;
2933 self::add_value_to_array( $name, $l2, $this_val, $vars[ $l1 ] );
2934 break;
2935
2936 case 2:
2937 $l3 = $name;
2938 self::add_value_to_array( $name, $l3, $this_val, $vars[ $l1 ][ $l2 ] );
2939 break;
2940
2941 case 3:
2942 $l4 = $name;
2943 self::add_value_to_array( $name, $l4, $this_val, $vars[ $l1 ][ $l2 ][ $l3 ] );
2944 }
2945
2946 unset( $this_val, $n );
2947 }//end foreach
2948
2949 unset( $last, $jv );
2950 }//end foreach
2951
2952 return $vars;
2953 }
2954
2955 /**
2956 * @param string $name
2957 * @param string $l1
2958 */
2959 public static function add_value_to_array( $name, $l1, $val, &$vars ) {
2960 if ( $name == '' ) {
2961 $vars[] = $val;
2962 } elseif ( ! isset( $vars[ $l1 ] ) ) {
2963 $vars[ $l1 ] = $val;
2964 }
2965 }
2966
2967 public static function maybe_add_tooltip( $name, $class = 'closed', $form_name = '' ) {
2968 $tooltips = array(
2969 'action_title' => __( 'Give this action a label for easy reference.', 'formidable' ),
2970 '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' ),
2971 'cc' => __( 'Add CC addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2972 'bcc' => __( 'Add BCC addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2973 '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' ),
2974 'from' => __( 'Enter the name and/or email address of the sender. FORMAT: John Bates <john@example.com> or john@example.com.', 'formidable' ),
2975 /* translators: %1$s: Form name, %2$s: Date */
2976 '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() ) ),
2977 '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' ),
2978 );
2979
2980 if ( ! isset( $tooltips[ $name ] ) ) {
2981 return;
2982 }
2983
2984 if ( 'open' == $class ) {
2985 echo ' frm_help"';
2986 } else {
2987 echo ' class="frm_help"';
2988 }
2989
2990 echo ' title="' . esc_attr( $tooltips[ $name ] );
2991
2992 if ( 'open' != $class ) {
2993 echo '"';
2994 }
2995 }
2996
2997 /**
2998 * Add the current_page class to that page in the form nav
2999 */
3000 public static function select_current_page( $page, $current_page, $action = array() ) {
3001 if ( $current_page != $page ) {
3002 return;
3003 }
3004
3005 $frm_action = self::simple_get( 'frm_action', 'sanitize_title' );
3006 if ( 'lite-reports' === $frm_action ) {
3007 $frm_action = 'reports';
3008 }
3009
3010 if ( empty( $action ) || ( ! empty( $frm_action ) && in_array( $frm_action, $action ) ) ) {
3011 echo ' class="current_page"';
3012 }
3013 }
3014
3015 /**
3016 * Prepare and json_encode post content
3017 *
3018 * @since 2.0
3019 *
3020 * @param array $post_content
3021 *
3022 * @return string $post_content ( json encoded array )
3023 */
3024 public static function prepare_and_encode( $post_content ) {
3025 // Loop through array to strip slashes and add only the needed ones.
3026 foreach ( $post_content as $key => $val ) {
3027 // Replace problematic characters (like &quot;)
3028 if ( is_string( $val ) ) {
3029 $val = str_replace( '&quot;', '"', $val );
3030 }
3031
3032 self::prepare_action_slashes( $val, $key, $post_content );
3033 unset( $key, $val );
3034 }
3035
3036 // json_encode the array.
3037 $post_content = json_encode( $post_content );
3038
3039 // Add extra slashes for \r\n since WP strips them.
3040 $post_content = str_replace( array( '\\r', '\\n', '\\u', '\\t' ), array( '\\\\r', '\\\\n', '\\\\u', '\\\\t' ), $post_content );
3041
3042 // allow for &quot
3043 $post_content = str_replace( '&quot;', '\\"', $post_content );
3044
3045 return $post_content;
3046 }
3047
3048 private static function prepare_action_slashes( $val, $key, &$post_content ) {
3049 if ( ! isset( $post_content[ $key ] ) || is_numeric( $val ) ) {
3050 return;
3051 }
3052
3053 if ( is_array( $val ) ) {
3054 foreach ( $val as $k1 => $v1 ) {
3055 self::prepare_action_slashes( $v1, $k1, $post_content[ $key ] );
3056 unset( $k1, $v1 );
3057 }
3058 } else {
3059 // Strip all slashes so everything is the same, no matter where the value is coming from
3060 $val = stripslashes( $val );
3061
3062 // Add backslashes before double quotes and forward slashes only
3063 $post_content[ $key ] = addcslashes( $val, '"\\/' );
3064 }
3065 }
3066
3067 /**
3068 * Check for either json or serialized data. This is temporary while transitioning
3069 * all data to json.
3070 *
3071 * @since 4.02.03
3072 *
3073 * @param array|string $value
3074 * @return void
3075 */
3076 public static function unserialize_or_decode( &$value ) {
3077 if ( is_array( $value ) ) {
3078 return;
3079 }
3080
3081 if ( is_serialized( $value ) ) {
3082 $value = self::maybe_unserialize_array( $value );
3083 } else {
3084 $value = self::maybe_json_decode( $value, false );
3085 }
3086 }
3087
3088 /**
3089 * Safely unserialize an array if necessary.
3090 * This function doesn't actually use unserialize. The string is parsed instead.
3091 *
3092 * @since 6.2
3093 *
3094 * @param mixed $value
3095 * @return mixed
3096 */
3097 public static function maybe_unserialize_array( $value ) {
3098 if ( ! is_string( $value ) ) {
3099 return $value;
3100 }
3101
3102 // Since we only expect an array, skip anything that doesn't start with a:.
3103 if ( ! is_serialized( $value ) || 'a:' !== substr( $value, 0, 2 ) ) {
3104 return $value;
3105 }
3106
3107 $parsed = FrmSerializedStringParserHelper::get()->parse( $value );
3108 if ( is_array( $parsed ) ) {
3109 $value = $parsed;
3110 }
3111
3112 return $value;
3113 }
3114
3115 /**
3116 * Decode a JSON string.
3117 * Do not switch shortcodes like [24] to array unless intentional ie XML values.
3118 *
3119 * @param mixed $string
3120 * @param bool $single_to_array
3121 * @return mixed
3122 */
3123 public static function maybe_json_decode( $string, $single_to_array = true ) {
3124 if ( is_array( $string ) || is_null( $string ) ) {
3125 return $string;
3126 }
3127
3128 $new_string = json_decode( $string, true );
3129 if ( function_exists( 'json_last_error' ) ) {
3130 // php 5.3+
3131 $single_value = false;
3132 if ( ! $single_to_array ) {
3133 $single_value = is_array( $new_string ) && count( $new_string ) === 1 && isset( $new_string[0] );
3134 }
3135 if ( json_last_error() == JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
3136 $string = $new_string;
3137 }
3138 }
3139
3140 return $string;
3141 }
3142
3143 /**
3144 * @since 6.2.3
3145 *
3146 * @param string $value
3147 * @return string
3148 */
3149 public static function maybe_utf8_encode( $value ) {
3150 $from_format = 'ISO-8859-1';
3151 $to_format = 'UTF-8';
3152
3153 if ( function_exists( 'mb_check_encoding' ) && function_exists( 'mb_convert_encoding' ) ) {
3154 if ( mb_check_encoding( $value, $from_format ) ) {
3155 return mb_convert_encoding( $value, $to_format, $from_format );
3156 }
3157 return $value;
3158 }
3159
3160 if ( function_exists( 'iconv' ) ) {
3161 $converted = iconv( $from_format, $to_format, $value );
3162 // Value is false if $value is not ISO-8859-1.
3163 if ( false !== $converted ) {
3164 return $converted;
3165 }
3166 }
3167
3168 return $value;
3169 }
3170
3171 /**
3172 * Reformat the json serialized array in name => value array.
3173 *
3174 * @since 4.02.03
3175 */
3176 public static function format_form_data( &$form ) {
3177 $formatted = array();
3178
3179 foreach ( $form as $input ) {
3180 if ( ! isset( $input['name'] ) ) {
3181 continue;
3182 }
3183 $key = $input['name'];
3184 if ( isset( $formatted[ $key ] ) ) {
3185 if ( is_array( $formatted[ $key ] ) ) {
3186 $formatted[ $key ][] = $input['value'];
3187 } else {
3188 $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
3189 }
3190 } else {
3191 $formatted[ $key ] = $input['value'];
3192 }
3193 }
3194
3195 parse_str( http_build_query( $formatted ), $form );
3196 }
3197
3198 /**
3199 * @since 4.02.03
3200 *
3201 * @param array|string $value
3202 * @return string
3203 */
3204 public static function maybe_json_encode( $value ) {
3205 if ( is_array( $value ) ) {
3206 $value = wp_json_encode( $value );
3207 }
3208 return $value;
3209 }
3210
3211 /**
3212 * Echo The javascript to open and highlight the Formidable menu
3213 *
3214 * @since 1.07.10
3215 *
3216 * @param string $post_type The name of the post type that may need to be highlighted.
3217 * @return void
3218 */
3219 public static function maybe_highlight_menu( $post_type ) {
3220 global $post;
3221
3222 if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] != $post_type ) {
3223 return;
3224 }
3225
3226 if ( is_object( $post ) && $post->post_type != $post_type ) {
3227 return;
3228 }
3229
3230 self::load_admin_wide_js();
3231 echo '<script type="text/javascript">jQuery(document).ready(function(){frmSelectSubnav();});</script>';
3232 }
3233
3234 /**
3235 * Load the JS file on non-Formidable pages in the admin area
3236 *
3237 * @since 2.0
3238 *
3239 * @param bool $load
3240 * @return void
3241 */
3242 public static function load_admin_wide_js( $load = true ) {
3243 $version = self::plugin_version();
3244 wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), $version );
3245
3246 $global_strings = array(
3247 'updating_msg' => __( 'Please wait while your site updates.', 'formidable' ),
3248 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
3249 'url' => self::plugin_url(),
3250 'app_url' => 'https://formidableforms.com/',
3251 'applicationsUrl' => admin_url( 'admin.php?page=formidable-applications' ),
3252 'canAccessApplicationDashboard' => current_user_can( is_callable( 'FrmProApplicationsHelper::get_required_templates_capability' ) ? FrmProApplicationsHelper::get_required_templates_capability() : 'frm_edit_forms' ),
3253 'loading' => __( 'Loading&hellip;', 'formidable' ),
3254 'nonce' => wp_create_nonce( 'frm_ajax' ),
3255 'proIncludesSliderJs' => is_callable( 'FrmProFormsHelper::prepare_custom_currency' ),
3256 'inboxSlideIn' => FrmInbox::get_inbox_slide_in_value_for_js(),
3257 );
3258 wp_localize_script( 'formidable_admin_global', 'frmGlobal', $global_strings );
3259
3260 if ( $load ) {
3261 wp_enqueue_script( 'formidable_admin_global' );
3262 }
3263 }
3264
3265 /**
3266 * @since 2.0.9
3267 * @return void
3268 */
3269 public static function load_font_style() {
3270 wp_enqueue_style( 'frm_fonts', self::plugin_url() . '/css/frm_fonts.css', array(), self::plugin_version() );
3271 }
3272
3273 /**
3274 * @param string $location
3275 * @return void
3276 */
3277 public static function localize_script( $location ) {
3278 global $wp_scripts;
3279
3280 $script_strings = array(
3281 'ajax_url' => esc_url_raw( self::get_ajax_url() ),
3282 'images_url' => self::plugin_url() . '/images',
3283 'loading' => __( 'Loading&hellip;', 'formidable' ),
3284 'remove' => __( 'Remove', 'formidable' ),
3285 'offset' => apply_filters( 'frm_scroll_offset', 4 ),
3286 'nonce' => wp_create_nonce( 'frm_ajax' ),
3287 'id' => __( 'ID', 'formidable' ),
3288 'no_results' => __( 'No results match', 'formidable' ),
3289 'file_spam' => __( 'That file looks like Spam.', 'formidable' ),
3290 'calc_error' => __( 'There is an error in the calculation in the field with key', 'formidable' ),
3291 'empty_fields' => __( 'Please complete the preceding required fields before uploading a file.', 'formidable' ),
3292 'focus_first_error' => self::should_focus_first_error(),
3293 'include_alert_role' => self::should_include_alert_role_on_field_errors(),
3294 // We need to keep this setting for a few versions because Pro checks for this.
3295 'include_resend_email' => false,
3296 );
3297
3298 $data = $wp_scripts->get_data( 'formidable', 'data' );
3299 if ( ! $data ) {
3300 wp_localize_script( 'formidable', 'frm_js', $script_strings );
3301 }
3302
3303 if ( $location === 'admin' ) {
3304 $admin_script_strings = array(
3305 'desc' => __( '(Click to add description)', 'formidable' ),
3306 'blank' => __( '(Blank)', 'formidable' ),
3307 'no_label' => __( '(no label)', 'formidable' ),
3308 'ok' => __( 'OK', 'formidable' ),
3309 'cancel' => __( 'Cancel', 'formidable' ),
3310 'default_label' => __( 'Default', 'formidable' ),
3311 'clear_default' => __( 'Clear default value when typing', 'formidable' ),
3312 'no_clear_default' => __( 'Do not clear default value when typing', 'formidable' ),
3313 'valid_default' => __( 'Default value will pass form validation', 'formidable' ),
3314 'no_valid_default' => __( 'Default value will NOT pass form validation', 'formidable' ),
3315 'confirm' => __( 'Are you sure?', 'formidable' ),
3316 'conf_delete' => __( 'Are you sure you want to delete this field and all data associated with it?', 'formidable' ),
3317 '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' ),
3318 'conf_no_repeat' => __( 'Warning: If you have entries with multiple rows, all but the first row will be lost.', 'formidable' ),
3319 'default_unique' => FrmFieldsHelper::default_unique_msg(),
3320 'default_conf' => __( 'The entered values do not match', 'formidable' ),
3321 'enter_email' => __( 'Enter Email', 'formidable' ),
3322 'confirm_email' => __( 'Confirm Email', 'formidable' ),
3323 'conditional_text' => __( 'Conditional content here', 'formidable' ),
3324 'new_option' => __( 'New Option', 'formidable' ),
3325 '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' ),
3326 'enter_password' => __( 'Enter Password', 'formidable' ),
3327 'confirm_password' => __( 'Confirm Password', 'formidable' ),
3328 'import_complete' => __( 'Import Complete', 'formidable' ),
3329 'updating' => __( 'Please wait while your site updates.', 'formidable' ),
3330 'no_save_warning' => __( 'Warning: There is no way to retrieve unsaved entries.', 'formidable' ),
3331 'private_label' => __( 'Private', 'formidable' ),
3332 'jquery_ui_url' => '',
3333 'pro_url' => is_callable( 'FrmProAppHelper::plugin_url' ) ? FrmProAppHelper::plugin_url() : '',
3334 'no_licenses' => __( 'No new licenses were found', 'formidable' ),
3335 'unmatched_parens' => __( 'This calculation has at least one unmatched ( ) { } [ ].', 'formidable' ),
3336 'view_shortcodes' => __( 'This calculation may have shortcodes that work in Views but not forms.', 'formidable' ),
3337 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
3338 /* translators: %d is the number of allowed actions per form */
3339 'only_one_action' => sprintf( __( 'This form action is limited to %d per form.', 'formidable' ), 1 ),
3340 'edit_action_text' => __( 'Please edit the existing form action.', 'formidable' ),
3341 'unsafe_params' => FrmFormsHelper::reserved_words(),
3342 /* Translators: %s is the name of a Detail Page Slug that is a reserved word.*/
3343 'slug_is_reserved' => sprintf( __( 'The Detail Page Slug "%s" is reserved by WordPress. This may cause problems. Is this intentional?', 'formidable' ), '****' ),
3344 /* 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. */
3345 'param_is_reserved' => sprintf( __( 'The parameter "%s" is reserved by WordPress. This may cause problems when included in the URL. Is this intentional? ', 'formidable' ), '****' ),
3346 'reserved_words' => __( 'See the list of reserved words in WordPress.', 'formidable' ),
3347 'repeat_limit_min' => __( 'Please enter a Repeat Limit that is greater than 1.', 'formidable' ),
3348 'checkbox_limit' => __( 'Please select a limit between 0 and 200.', 'formidable' ),
3349 'install' => __( 'Install', 'formidable' ),
3350 'active' => __( 'Active', 'formidable' ),
3351 'installed' => __( 'Installed', 'formidable' ),
3352 'not_installed' => __( 'Not Installed', 'formidable' ),
3353 'select_a_field' => __( 'Select a Field', 'formidable' ),
3354 'no_items_found' => __( 'No items found.', 'formidable' ),
3355 'field_already_used' => __( 'Oops. You have already used that field.', 'formidable' ),
3356
3357 // Deprecated in 6.0.
3358 'saving' => '',
3359
3360 // Deprecated in 6.0.
3361 'saved' => '',
3362
3363 // translators: %1$s: HTML open tag, %2$s: HTML end tag.
3364 'holdShiftMsg' => esc_html__( 'You can hold %1$sShift%2$s on your keyboard to select multiple fields', 'formidable' ),
3365 );
3366 /**
3367 * @param array $admin_script_strings
3368 */
3369 $admin_script_strings = apply_filters( 'frm_admin_script_strings', $admin_script_strings );
3370
3371 $data = $wp_scripts->get_data( 'formidable_admin', 'data' );
3372 if ( ! $data ) {
3373 wp_localize_script( 'formidable_admin', 'frm_admin_js', $admin_script_strings );
3374 }
3375 }//end if
3376 }
3377
3378 /**
3379 * @since 6.5
3380 *
3381 * @return string
3382 */
3383 public static function get_ajax_url() {
3384 $ajax_url = admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' );
3385
3386 /**
3387 * @since 2.0.13
3388 *
3389 * @param string $ajax_url
3390 */
3391 return apply_filters( 'frm_ajax_url', $ajax_url );
3392 }
3393
3394 /**
3395 * Returns whether or not the first errored input should be auto-focused (default true).
3396 *
3397 * @since 5.2.05
3398 *
3399 * @return bool
3400 */
3401 private static function should_focus_first_error() {
3402 return (bool) apply_filters( 'frm_focus_first_error', true );
3403 }
3404
3405 /**
3406 * Returns whether or not field errors should include role="alert" (default true).
3407 *
3408 * @since 5.2.05
3409 *
3410 * @return bool
3411 */
3412 public static function should_include_alert_role_on_field_errors() {
3413 return (bool) apply_filters( 'frm_include_alert_role_on_field_errors', true );
3414 }
3415
3416 /**
3417 * Echo the message on the plugins listing page
3418 *
3419 * @since 1.07.10
3420 *
3421 * @param float $min_version The version the add-on requires.
3422 * @return void
3423 */
3424 public static function min_version_notice( $min_version ) {
3425 $frm_version = self::plugin_version();
3426
3427 // Check if Formidable meets minimum requirements.
3428 if ( version_compare( $frm_version, $min_version, '>=' ) ) {
3429 return;
3430 }
3431
3432 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
3433 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">' .
3434 esc_html__( 'You are running an outdated version of Formidable. This plugin may not work correctly if you do not update Formidable.', 'formidable' ) .
3435 '</div></td></tr>';
3436 }
3437
3438 /**
3439 * If Pro is far outdated, show a message.
3440 *
3441 * @since 4.0.01
3442 *
3443 * @return void
3444 */
3445 public static function min_pro_version_notice( $min_version ) {
3446 if ( ! self::is_formidable_admin() ) {
3447 // Don't show admin-wide.
3448 return;
3449 }
3450
3451 self::php_version_notice();
3452
3453 $is_pro = self::pro_is_installed() && class_exists( 'FrmProDb' );
3454 if ( ! $is_pro || self::meets_min_pro_version( $min_version ) ) {
3455 return;
3456 }
3457
3458 $expired = FrmAddonsController::is_license_expired();
3459 ?>
3460 <div class="frm-banner-alert frm_error_style frm_previous_install">
3461 <?php
3462 esc_html_e( 'You are running a version of Formidable Forms that may not be compatible with your version of Formidable Forms Pro.', 'formidable' );
3463 if ( empty( $expired ) ) {
3464 echo ' Please <a href="' . esc_url( admin_url( 'plugins.php?s=formidable%20forms%20pro' ) ) . '">update now</a>.';
3465 } else {
3466 echo '<br/>Please <a href="https://formidableforms.com/account/downloads/?utm_source=WordPress&utm_medium=outdated">renew now</a> to get the latest version.';
3467 }
3468 ?>
3469 </div>
3470 <?php
3471 }
3472
3473 /**
3474 * If Pro is installed, check the version number.
3475 *
3476 * @since 4.0.01
3477 *
3478 * @param string $min_version
3479 * @return bool
3480 */
3481 public static function meets_min_pro_version( $min_version ) {
3482 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
3483 }
3484
3485 /**
3486 * Show a message if the browser or PHP version is below the recommendations.
3487 *
3488 * @since 4.0.02
3489 * @return void
3490 */
3491 private static function php_version_notice() {
3492 $message = array();
3493 if ( version_compare( phpversion(), '7.0', '<' ) ) {
3494 $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' );
3495 }
3496
3497 $browser = self::get_server_value( 'HTTP_USER_AGENT' );
3498 $is_ie = strpos( $browser, 'MSIE' ) !== false;
3499 if ( $is_ie ) {
3500 $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' );
3501 }
3502
3503 foreach ( $message as $m ) {
3504 ?>
3505 <div class="frm-banner-alert frm_error_style frm_previous_install">
3506 <?php echo esc_html( $m ); ?>
3507 </div>
3508 <?php
3509 }
3510 }
3511
3512 /**
3513 * @param string $type
3514 * @return array<string,string>
3515 */
3516 public static function locales( $type = 'date' ) {
3517 $locales = array(
3518 'en' => __( 'English', 'formidable' ),
3519 'af' => __( 'Afrikaans', 'formidable' ),
3520 'sq' => __( 'Albanian', 'formidable' ),
3521 'ar-DZ' => __( 'Algerian Arabic', 'formidable' ),
3522 'am' => __( 'Amharic', 'formidable' ),
3523 'ar' => __( 'Arabic', 'formidable' ),
3524 'hy' => __( 'Armenian', 'formidable' ),
3525 'az' => __( 'Azerbaijani', 'formidable' ),
3526 'eu' => __( 'Basque', 'formidable' ),
3527 'be' => __( 'Belarusian', 'formidable' ),
3528 'bn' => __( 'Bengali', 'formidable' ),
3529 'bs' => __( 'Bosnian', 'formidable' ),
3530 'bg' => __( 'Bulgarian', 'formidable' ),
3531 'ca' => __( 'Catalan', 'formidable' ),
3532 'zh-HK' => __( 'Chinese Hong Kong', 'formidable' ),
3533 'zh-CN' => __( 'Chinese Simplified', 'formidable' ),
3534 'zh-TW' => __( 'Chinese Traditional', 'formidable' ),
3535 'hr' => __( 'Croatian', 'formidable' ),
3536 'cs' => __( 'Czech', 'formidable' ),
3537 'da' => __( 'Danish', 'formidable' ),
3538 'nl' => __( 'Dutch', 'formidable' ),
3539 'en-GB' => __( 'English/UK', 'formidable' ),
3540 'eo' => __( 'Esperanto', 'formidable' ),
3541 'et' => __( 'Estonian', 'formidable' ),
3542 'fo' => __( 'Faroese', 'formidable' ),
3543 'fa' => __( 'Farsi/Persian', 'formidable' ),
3544 'fil' => __( 'Filipino', 'formidable' ),
3545 'fi' => __( 'Finnish', 'formidable' ),
3546 'fr' => __( 'French', 'formidable' ),
3547 'fr-CA' => __( 'French/Canadian', 'formidable' ),
3548 'fr-CH' => __( 'French/Swiss', 'formidable' ),
3549 'gl' => __( 'Galician', 'formidable' ),
3550 'ka' => __( 'Georgian', 'formidable' ),
3551 'de' => __( 'German', 'formidable' ),
3552 'de-AT' => __( 'German/Austria', 'formidable' ),
3553 'de-CH' => __( 'German/Switzerland', 'formidable' ),
3554 'el' => __( 'Greek', 'formidable' ),
3555 'gu' => __( 'Gujarati', 'formidable' ),
3556 'he' => __( 'Hebrew', 'formidable' ),
3557 'iw' => __( 'Hebrew', 'formidable' ),
3558 'hi' => __( 'Hindi', 'formidable' ),
3559 'hu' => __( 'Hungarian', 'formidable' ),
3560 'is' => __( 'Icelandic', 'formidable' ),
3561 'id' => __( 'Indonesian', 'formidable' ),
3562 'it' => __( 'Italian', 'formidable' ),
3563 'ja' => __( 'Japanese', 'formidable' ),
3564 'kn' => __( 'Kannada', 'formidable' ),
3565 'kk' => __( 'Kazakh', 'formidable' ),
3566 'km' => __( 'Khmer', 'formidable' ),
3567 'ko' => __( 'Korean', 'formidable' ),
3568 'ky' => __( 'Kyrgyz', 'formidable' ),
3569 'lo' => __( 'Laothian', 'formidable' ),
3570 'lv' => __( 'Latvian', 'formidable' ),
3571 'lt' => __( 'Lithuanian', 'formidable' ),
3572 'lb' => __( 'Luxembourgish', 'formidable' ),
3573 'mk' => __( 'Macedonian', 'formidable' ),
3574 'ml' => __( 'Malayalam', 'formidable' ),
3575 'ms' => __( 'Malaysian', 'formidable' ),
3576 'mr' => __( 'Marathi', 'formidable' ),
3577 'no' => __( 'Norwegian', 'formidable' ),
3578 'nb' => __( 'Norwegian Bokmål', 'formidable' ),
3579 'nn' => __( 'Norwegian Nynorsk', 'formidable' ),
3580 'pl' => __( 'Polish', 'formidable' ),
3581 'pt' => __( 'Portuguese', 'formidable' ),
3582 'pt-BR' => __( 'Portuguese/Brazilian', 'formidable' ),
3583 'pt-PT' => __( 'Portuguese/Portugal', 'formidable' ),
3584 'rm' => __( 'Romansh', 'formidable' ),
3585 'ro' => __( 'Romanian', 'formidable' ),
3586 'ru' => __( 'Russian', 'formidable' ),
3587 'sr' => __( 'Serbian', 'formidable' ),
3588 'sr-SR' => __( 'Serbian', 'formidable' ),
3589 'si' => __( 'Sinhalese', 'formidable' ),
3590 'sk' => __( 'Slovak', 'formidable' ),
3591 'sl' => __( 'Slovenian', 'formidable' ),
3592 'es' => __( 'Spanish', 'formidable' ),
3593 'es-419' => __( 'Spanish/Latin America', 'formidable' ),
3594 'sw' => __( 'Swahili', 'formidable' ),
3595 'sv' => __( 'Swedish', 'formidable' ),
3596 'ta' => __( 'Tamil', 'formidable' ),
3597 'te' => __( 'Telugu', 'formidable' ),
3598 'th' => __( 'Thai', 'formidable' ),
3599 'tj' => __( 'Tajiki', 'formidable' ),
3600 'tr' => __( 'Turkish', 'formidable' ),
3601 'uk' => __( 'Ukrainian', 'formidable' ),
3602 'ur' => __( 'Urdu', 'formidable' ),
3603 'vi' => __( 'Vietnamese', 'formidable' ),
3604 'cy-GB' => __( 'Welsh', 'formidable' ),
3605 'zu' => __( 'Zulu', 'formidable' ),
3606 );
3607
3608 if ( $type === 'captcha' ) {
3609 // remove the languages unavailable for the captcha
3610 $unset = array( 'sq', 'bs', 'eo', 'fo', 'fr-CH', 'sr-SR', 'ar-DZ', 'be', 'cy-GB', 'kk', 'km', 'ky', 'lb', 'mk', 'nb', 'nn', 'rm', 'tj' );
3611 } else {
3612 // remove the languages unavailable for the datepicker
3613 $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' );
3614 }
3615
3616 $locales = array_diff_key( $locales, array_flip( $unset ) );
3617
3618 /**
3619 * Filter available locale options.
3620 *
3621 * @since 5.4.5 Added $args parameter with type.
3622 *
3623 * @param array<string,string> $locales
3624 * @param array $args {
3625 * @type string $type
3626 * }
3627 */
3628 $locales = apply_filters( 'frm_locales', $locales, compact( 'type' ) );
3629
3630 return $locales;
3631 }
3632
3633 /**
3634 * @return string
3635 */
3636 public static function get_menu_icon_class() {
3637 if ( is_callable( 'FrmProAppHelper::get_settings' ) ) {
3638 $settings = FrmProAppHelper::get_settings();
3639 if ( is_object( $settings ) && ! empty( $settings->menu_icon ) ) {
3640 return $settings->menu_icon;
3641 }
3642 }
3643 return 'frmfont frm_logo_icon';
3644 }
3645
3646 /**
3647 * Shows the images dropdown.
3648 *
3649 * @since 5.0.04
3650 *
3651 * @param array $args {
3652 * Arguments.
3653 *
3654 * @type string $selected Selected value.
3655 * @type array[] $options Array of options with keys are option values and values are array.
3656 * The option array contains `text`, `svg` and `custom_atts`.
3657 * @type string $classes Custom CSS classes for the wrapper element.
3658 * @type array $input_attrs Attributes of value input.
3659 * }
3660 */
3661 public static function images_dropdown( $args ) {
3662 $args = self::fill_default_images_dropdown_args( $args );
3663
3664 $input_attrs_str = self::get_images_dropdown_input_attrs( $args );
3665 ob_start();
3666 include self::plugin_path() . '/classes/views/shared/images-dropdown.php';
3667 $output = ob_get_clean();
3668
3669 /**
3670 * Allows modifying the output of FrmAppHelper::images_dropdown() method.
3671 *
3672 * @since 5.0.04
3673 *
3674 * @param string $output The output.
3675 * @param array $args Passed arguments.
3676 */
3677 echo apply_filters( 'frm_images_dropdown_output', $output, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3678 }
3679
3680 /**
3681 * Fills the default images_dropdown() arguments.
3682 *
3683 * @since 5.0.04
3684 *
3685 * @param array $args The arguments.
3686 * @return array
3687 */
3688 private static function fill_default_images_dropdown_args( $args ) {
3689 $defaults = array(
3690 'selected' => '',
3691 'options' => array(),
3692 'classes' => '',
3693 'input_attrs' => array(),
3694 );
3695 $new_args = wp_parse_args( $args, $defaults );
3696
3697 $new_args['options'] = (array) $new_args['options'];
3698 $new_args['input_attrs'] = (array) $new_args['input_attrs'];
3699
3700 // Set the number of columns.
3701 $new_args['col_class'] = ceil( 12 / count( $new_args['options'] ) );
3702 if ( $new_args['col_class'] > 6 ) {
3703 $new_args['col_class'] = ceil( $new_args['col_class'] / 2 );
3704 }
3705
3706 /**
3707 * Allows modifying the arguments of images_dropdown() method.
3708 *
3709 * @since 5.0.04
3710 *
3711 * @param array $new_args Arguments after filling the defaults.
3712 * @param array $args Arguments passed to the method, before filling the defaults.
3713 */
3714 return apply_filters( 'frm_images_dropdown_args', $new_args, $args );
3715 }
3716
3717 /**
3718 * Gets HTML attributes of the input in images_dropdown() method.
3719 *
3720 * @since 5.0.04
3721 *
3722 * @param array $args The arguments.
3723 * @return string
3724 */
3725 private static function get_images_dropdown_input_attrs( $args ) {
3726 $input_attrs = $args['input_attrs'];
3727 $input_attrs['type'] = 'radio';
3728 $input_attrs['name'] = $args['name'];
3729
3730 $input_attrs_str = '';
3731 foreach ( $input_attrs as $key => $input_attr ) {
3732 $input_attrs_str .= ' ' . sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $input_attr ) );
3733 }
3734
3735 /**
3736 * Allows modifying the HTML attributes of the input in images_dropdown() method.
3737 *
3738 * @since 5.0.04
3739 *
3740 * @param string $input_attrs_str HTML attributes string.
3741 * @param array $args The arguments of images_dropdown() method.
3742 */
3743 return apply_filters( 'frm_images_dropdown_input_attrs', $input_attrs_str, $args );
3744 }
3745
3746 /**
3747 * @since 6.7.1
3748 */
3749 public static function get_images_dropdown_atts( $option, $args ) {
3750 $image = self::get_images_dropdown_option_image( $option, $args );
3751 $classes = self::get_images_dropdown_option_classes( $option, $args );
3752 $custom_attrs = self::get_images_dropdown_option_html_attrs( $option, $args );
3753 return compact( 'image', 'classes', 'custom_attrs' );
3754 }
3755
3756 /**
3757 * Gets the image of each option in images_dropdown() method.
3758 *
3759 * @since 5.0.04
3760 *
3761 * @param array $option Option data.
3762 * @param array $args The arguments of images_dropdown() method.
3763 * @return string
3764 */
3765 private static function get_images_dropdown_option_image( $option, $args ) {
3766 $image = self::icon_by_class(
3767 'frmfont ' . $option['svg'],
3768 array(
3769 'echo' => false,
3770 )
3771 );
3772
3773 $args['option'] = $option;
3774
3775 /**
3776 * Allows modifying the image of each option in images_dropdown() method.
3777 *
3778 * @since 5.0.04
3779 *
3780 * @param string $image The image HTML.
3781 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3782 */
3783 return apply_filters( 'frm_images_dropdown_option_image', $image, $args );
3784 }
3785
3786 /**
3787 * Gets the HTML classes of each option in images_dropdown() method.
3788 *
3789 * @since 5.0.04
3790 *
3791 * @param array $option Option data.
3792 * @param array $args The arguments of images_dropdown() method.
3793 * @return string
3794 */
3795 private static function get_images_dropdown_option_classes( $option, $args ) {
3796 $classes = '';
3797
3798 if ( ! empty( $option['custom_attrs']['class'] ) ) {
3799 $classes .= ' ' . $option['custom_attrs']['class'];
3800 }
3801
3802 $args['option'] = $option;
3803
3804 /**
3805 * Allows modifying the CSS classes of each option in images_dropdown() method.
3806 *
3807 * @since 5.0.04
3808 *
3809 * @param string $classes CSS classes.
3810 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3811 */
3812 return apply_filters( 'frm_images_dropdown_option_classes', $classes, $args );
3813 }
3814
3815 /**
3816 * Gets the custom HTML attributes 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_html_attrs( $option, $args ) {
3825 $html_attrs = '';
3826 if ( ! empty( $option['custom_attrs'] ) && is_array( $option['custom_attrs'] ) ) {
3827 $html_attrs_arr = array();
3828
3829 foreach ( $option['custom_attrs'] as $key => $value ) {
3830 if ( in_array( $key, array( 'type', 'class', 'data-value' ) ) ) {
3831 continue;
3832 }
3833
3834 $html_attrs_arr[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
3835 }
3836
3837 $html_attrs = implode( ' ', $html_attrs_arr );
3838 }
3839
3840 $args['option'] = $option;
3841
3842 /**
3843 * Allows modifying the custom HTML attributes of each option in images_dropdown() method.
3844 *
3845 * @since 5.0.04
3846 *
3847 * @param string $html_attrs The HTML attributes string.
3848 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3849 */
3850 return apply_filters( 'frm_images_dropdown_option_html_attrs', $html_attrs, $args );
3851 }
3852
3853 /**
3854 * @since 5.0.07
3855 *
3856 * @return bool true if the current user is allowed to save unfiltered HTML.
3857 */
3858 public static function allow_unfiltered_html() {
3859 if ( self::should_never_allow_unfiltered_html() ) {
3860 return false;
3861 }
3862 return current_user_can( 'unfiltered_html' );
3863 }
3864
3865 /**
3866 * @since 5.0.13
3867 *
3868 * @return bool
3869 */
3870 public static function should_never_allow_unfiltered_html() {
3871 if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) {
3872 return true;
3873 }
3874
3875 /**
3876 * Formidable will check DISALLOW_UNFILTERED_HTML to determine if some form HTML should be filtered or not.
3877 * In many cases, scripts are added intentionally to forms and will not be stripped if DISALLOW_UNFILTERED_HTML is not set.
3878 * It is also possible to filter Formidable without defining DISALLOW_UNFILTERED_HTML, with add_filter( 'frm_disallow_unfiltered_html', '__return_true' );
3879 *
3880 * @since 5.0.13
3881 */
3882 return apply_filters( 'frm_disallow_unfiltered_html', false );
3883 }
3884
3885 /**
3886 * @since 5.0.07
3887 *
3888 * @param array $values
3889 * @param array $keys
3890 * @return array
3891 */
3892 public static function maybe_filter_array( $values, $keys ) {
3893 $allow_unfiltered_html = self::allow_unfiltered_html();
3894
3895 if ( $allow_unfiltered_html ) {
3896 return $values;
3897 }
3898
3899 foreach ( $keys as $key ) {
3900 if ( isset( $values[ $key ] ) ) {
3901 $values[ $key ] = self::kses( $values[ $key ], 'all' );
3902 }
3903 }
3904
3905 return $values;
3906 }
3907
3908 /**
3909 * Some back end fields allow privileged users to add scripts.
3910 * A site that uses the DISALLOW_UNFILTERED_HTML always remove scripts on echo.
3911 *
3912 * @since 5.0.13
3913 *
3914 * @param string $value
3915 * @param array|string $allowed 'all' for everything included as defaults.
3916 * @return string
3917 */
3918 public static function maybe_kses( $value, $allowed = 'all' ) {
3919 if ( self::should_never_allow_unfiltered_html() ) {
3920 $value = self::kses( $value, $allowed );
3921 }
3922 return $value;
3923 }
3924
3925 /**
3926 * Check if an option attribute used in an [input] shortcode is safe.
3927 *
3928 * @since 6.11.2
3929 *
3930 * @param string $key
3931 * @param string $context Either 'display' or 'update'. On update, we want to allow a few keys that are never displayed.
3932 * @return bool
3933 */
3934 public static function input_key_is_safe( $key, $context = 'display' ) {
3935 if ( 'update' === $context && in_array( $key, array( 'opt', 'label' ), true ) ) {
3936 $safe = true;
3937 } elseif ( 0 === strpos( $key, 'data-' ) ) {
3938 // Allow all data attributes.
3939 $safe = true;
3940 } elseif ( 0 === strpos( $key, 'aria-' ) ) {
3941 // Allow all aria attributes.
3942 $safe = true;
3943 } else {
3944 $safe_keys = array(
3945 'class',
3946 'required',
3947 'title',
3948 'placeholder',
3949 'value',
3950 'readonly',
3951 'disabled',
3952 'size',
3953 'maxlength',
3954 'min',
3955 'max',
3956 'pattern',
3957 'step',
3958 'autofocus',
3959 'width',
3960 'height',
3961 'autocomplete',
3962 'tabindex',
3963 'role',
3964 'style',
3965 );
3966 $safe = in_array( $key, $safe_keys, true );
3967 }//end if
3968
3969 /**
3970 * Filter the $safe value so additional keys can be allowed or disallowed.
3971 *
3972 * @since 6.11.2
3973 *
3974 * @param bool $safe True if the key is considered safe.
3975 * @param string $key
3976 * @param string $context Either 'display' or 'update'.
3977 */
3978 return (bool) apply_filters( 'frm_input_key_is_safe', $safe, $key, $context );
3979 }
3980
3981 /**
3982 * @since 5.0.16
3983 *
3984 * @return bool
3985 */
3986 public static function show_landing_pages() {
3987 return self::show_new_feature( 'landing' );
3988 }
3989
3990 /**
3991 * @since 5.0.16
3992 *
3993 * @return array
3994 */
3995 public static function get_landing_page_upgrade_data_params( $medium = 'landing' ) {
3996 $params = array(
3997 'medium' => $medium,
3998 'upgrade' => __( 'Form Landing Pages', 'formidable' ),
3999 'message' => __( 'Easily manage a landing page for your form. Upgrade to get form landing pages.', 'formidable' ),
4000 'screenshot' => 'landing.png',
4001 );
4002 return self::get_upgrade_data_params( 'landing', $params );
4003 }
4004
4005 /**
4006 * @since 5.0.17
4007 *
4008 * @param string $feature
4009 * @return bool
4010 */
4011 public static function show_new_feature( $feature ) {
4012 $link = FrmAddonsController::install_link( $feature );
4013 return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );
4014 }
4015
4016 /**
4017 * @since 5.0.17
4018 *
4019 * @param string $plugin
4020 * @param array $params
4021 * @return array
4022 */
4023 public static function get_upgrade_data_params( $plugin, $params ) {
4024 $link = FrmAddonsController::install_link( $plugin );
4025 if ( ! $link ) {
4026 return $params;
4027 }
4028
4029 if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
4030 $params['oneclick'] = json_encode( $link );
4031 unset( $params['message'] );
4032 if ( ! isset( $params['medium'] ) ) {
4033 $params['medium'] = $plugin;
4034 }
4035 } else {
4036 $params['requires'] = FrmFormsHelper::get_plan_required( $link );
4037 }
4038
4039 return $params;
4040 }
4041
4042 /**
4043 * 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.
4044 * Not every server installs the ctype extension, so use a fallback if the function does not exist.
4045 *
4046 * @since 5.0.17
4047 *
4048 * @param string $text
4049 * @return bool
4050 */
4051 public static function ctype_xdigit( $text ) {
4052 if ( function_exists( 'ctype_xdigit' ) ) {
4053 return ctype_xdigit( $text );
4054 }
4055 return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Fa-f0-9]/', $text );
4056 }
4057
4058 /**
4059 * Set the current screen to avoid undefined notices.
4060 *
4061 * @since 5.2.01
4062 */
4063 public static function set_current_screen_and_hook_suffix() {
4064 global $hook_suffix;
4065 if ( is_null( $hook_suffix ) ) {
4066 // $hook_suffix gets used in substr so make sure it's not null. PHP 8.1 deprecates null in substr.
4067 $hook_suffix = ''; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
4068 }
4069 set_current_screen();
4070 }
4071
4072 /**
4073 * Shows pill text.
4074 *
4075 * @since 5.2.02
4076 *
4077 * @param string $text Text in the pill. Default is NEW.
4078 */
4079 public static function show_pill_text( $text = null ) {
4080 if ( null === $text ) {
4081 $text = __( 'NEW', 'formidable' );
4082 }
4083 echo '<span class="frm-meta-tag frm-new-pill">' . esc_html( $text ) . '</span>';
4084 }
4085
4086 /**
4087 * Count the number of decimals digits.
4088 *
4089 * @since 5.2.07
4090 *
4091 * @param mixed $num Number.
4092 * @return false|int Returns `false` if the passed parameter is not number.
4093 */
4094 public static function count_decimals( $num ) {
4095 if ( ! is_numeric( $num ) ) {
4096 return false;
4097 }
4098
4099 $num = (string) $num;
4100 $parts = explode( '.', $num );
4101 if ( 1 === count( $parts ) ) {
4102 return 0;
4103 }
4104
4105 return strlen( $parts[ count( $parts ) - 1 ] );
4106 }
4107
4108 /**
4109 * Prevent a fatal error in PHP8 if gmt_offset happens to be set an empty string.
4110 * This is a bug in WordPress. It isn't safe to call current_time( 'timestamp' ) without this with an empty string offset.
4111 * In the future this might be safe to remove. Keep an eye on the current_time function in functions.php.
4112 *
4113 * @since 5.3.1
4114 *
4115 * @return void
4116 */
4117 public static function filter_gmt_offset() {
4118 if ( self::$added_gmt_offset_filter ) {
4119 // Avoid adding twice.
4120 return;
4121 }
4122
4123 add_filter(
4124 'option_gmt_offset',
4125 function ( $offset ) {
4126 if ( ! is_string( $offset ) || is_numeric( $offset ) ) {
4127 // Leave a valid value alone.
4128 return $offset;
4129 }
4130
4131 return 0;
4132 }
4133 );
4134 self::$added_gmt_offset_filter = true;
4135 }
4136
4137 /**
4138 * @since 5.3.1
4139 *
4140 * @return bool
4141 */
4142 public static function on_form_listing_page() {
4143 if ( ! self::is_admin_page( 'formidable' ) ) {
4144 return false;
4145 }
4146
4147 $action = self::simple_get( 'frm_action', 'sanitize_title' );
4148 return ! $action || in_array( $action, self::get_form_listing_page_actions(), true );
4149 }
4150
4151 /**
4152 * Get all actions that also display the forms list.
4153 *
4154 * @since 5.3.1
4155 *
4156 * @return array<string>
4157 */
4158 private static function get_form_listing_page_actions() {
4159 return array( 'list', 'trash', 'untrash', 'destroy' );
4160 }
4161
4162 /**
4163 * Safely call get_plugins, importing the required files if they are not yet loaded.
4164 *
4165 * @since 5.5
4166 *
4167 * @return array
4168 */
4169 public static function get_plugins() {
4170 if ( ! function_exists( 'get_plugins' ) ) {
4171 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4172 }
4173 return get_plugins();
4174 }
4175
4176 /**
4177 * 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.
4178 * This is to make sure that the URL can't be exploited for a SSRF attack.
4179 *
4180 * @since 5.5.5
4181 *
4182 * @param string $url
4183 * @param string $expected_extension
4184 * @return bool
4185 */
4186 public static function validate_url_is_in_s3_bucket( $url, $expected_extension ) {
4187 $file_is_in_expected_s3_bucket = 0 === strpos( $url, 'https://s3.amazonaws.com/fp.strategy11.com' );
4188 if ( ! $file_is_in_expected_s3_bucket ) {
4189 return false;
4190 }
4191
4192 $parsed = parse_url( $url );
4193 if ( ! is_array( $parsed ) ) {
4194 // URL is malformed.
4195 return false;
4196 }
4197
4198 $path = $parsed['path'];
4199 $ext = pathinfo( $path, PATHINFO_EXTENSION );
4200 if ( $expected_extension !== $ext ) {
4201 // The URL isn't to an XML file.
4202 return false;
4203 }
4204
4205 return true;
4206 }
4207
4208 /**
4209 * Display a dismissable warning message and save its dismissal state.
4210 *
4211 * @since 6.3
4212 *
4213 * @param string $message The warning message to display.
4214 * @param string $option The unique identifier for the dismissal state of the message and the WP Ajax action.
4215 * @return void
4216 */
4217 public static function add_dismissable_warning_message( $message = '', $option = '' ) {
4218 if ( ! $message || ! $option ) {
4219 return;
4220 }
4221
4222 $ajax_callback = function () use ( $option ) {
4223 self::dismiss_warning_message( $option );
4224 };
4225
4226 // We're handling JS codes with `doJsonPost` and it adds 'frm_' to the beginning of the action.
4227 // To prevent any issues, we add 'frm_' from the beginning of the action.
4228 add_action( 'wp_ajax_frm_' . $option, $ajax_callback );
4229
4230 add_filter(
4231 'frm_message_list',
4232 function ( $show_messages ) use ( $message, $option ) {
4233 if ( get_option( $option, false ) ) {
4234 return $show_messages;
4235 }
4236
4237 $dismiss_icon = self::icon_by_class(
4238 'frmfont frm_close_icon',
4239 array(
4240 'aria-label' => _x( 'Dismiss', 'warning message: close icon label', 'formidable' ),
4241 'echo' => false,
4242 )
4243 );
4244
4245 $show_messages[] = $message;
4246 $show_messages[] = '<span class="frm-warning-dismiss frmsvg" data-action="' . esc_attr( $option ) . '">' . $dismiss_icon . '</span>';
4247
4248 return $show_messages;
4249 }
4250 );
4251 }
4252
4253 /**
4254 * Dismiss a warning message and update the dismissal state.
4255 *
4256 * @since 6.3
4257 *
4258 * @param string $option The unique identifier for the dismissal state of the message.
4259 * @return void
4260 */
4261 public static function dismiss_warning_message( $option = '' ) {
4262 self::permission_check( 'frm_change_settings' );
4263 check_ajax_referer( 'frm_ajax', 'nonce' );
4264
4265 if ( $option ) {
4266 update_option( $option, true, 'no' );
4267 }
4268
4269 wp_send_json_success();
4270 }
4271
4272 /**
4273 * Lite license copy.
4274 * Used in FrmDashboardController & FrmSettingsController
4275 *
4276 * @since 6.8
4277 *
4278 * @return string
4279 */
4280 public static function copy_for_lite_license() {
4281 $message = __( 'You\'re using Formidable Forms Lite - no license needed. Enjoy!', 'formidable' ) . ' 🙂';
4282
4283 if ( is_callable( 'FrmProAddonsController::get_readable_license_type' ) && ! class_exists( 'FrmProDashboardController' ) ) {
4284 // Manage PRO versions without PRO dashboard functionality.
4285 $license_type = FrmProAddonsController::get_readable_license_type();
4286 if ( 'lite' !== strtolower( $license_type ) ) {
4287 $message = 'Formidable Pro ' . $license_type;
4288 }
4289 }
4290
4291 return apply_filters( 'frm_license_type_text', $message );
4292 }
4293
4294 /**
4295 * Removes scripts that are unnecessarily loaded across the pages!
4296 *
4297 * @since 6.9
4298 * @return void
4299 */
4300 public static function dequeue_extra_global_scripts() {
4301 wp_dequeue_script( 'frm-surveys-admin' );
4302 wp_dequeue_script( 'frm-quizzes-form-action' );
4303 }
4304
4305 /**
4306 * Shows tooltip icon.
4307 *
4308 * @since 6.12
4309 *
4310 * @param string $tooltip_text Tooltip text.
4311 * @param array $atts Tooltip wrapper HTML attributes.
4312 *
4313 * @return void
4314 */
4315 public static function tooltip_icon( $tooltip_text, $atts = array() ) {
4316 $atts['title'] = $tooltip_text;
4317 if ( isset( $atts['class'] ) ) {
4318 $atts['class'] .= ' frm_help';
4319 } else {
4320 $atts['class'] = 'frm_help';
4321 }
4322 ?>
4323 <span <?php self::array_to_html_params( $atts, true ); ?>>
4324 <?php self::icon_by_class( 'frmfont frm_tooltip_icon' ); ?>
4325 </span>
4326 <?php
4327 }
4328 }
4329