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

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