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

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