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

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