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

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