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

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