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

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

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