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

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