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

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