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

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