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

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

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