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

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