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

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