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

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