PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.7
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.7
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / classes / helpers / FrmAppHelper.php

FrmAppHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.7, at classes/helpers/FrmAppHelper.php

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