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

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