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

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