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

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