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

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