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

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

3,761 lines 104.4 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.4.4';
20
21 /**
22 * @since 1.07.02
23 *
24 * @param none
25 *
26 * @return string The version of this plugin
27 */
28 public static function plugin_version() {
29 return self::$plug_version;
30 }
31
32 public static function plugin_folder() {
33 return basename( self::plugin_path() );
34 }
35
36 public static function plugin_path() {
37 return dirname( dirname( dirname( __FILE__ ) ) );
38 }
39
40 public static function plugin_url() {
41 // Prevously FRM_URL constant.
42 return plugins_url( '', self::plugin_path() . '/formidable.php' );
43 }
44
45 public static function relative_plugin_url() {
46 return str_replace( array( 'https:', 'http:' ), '', self::plugin_url() );
47 }
48
49 /**
50 * @return string Site URL
51 */
52 public static function site_url() {
53 return site_url();
54 }
55
56 /**
57 * Get the name of this site
58 * Used for [sitename] shortcode
59 *
60 * @since 2.0
61 * @return string
62 */
63 public static function site_name() {
64 return get_option( 'blogname' );
65 }
66
67 public static function make_affiliate_url( $url ) {
68 $affiliate_id = self::get_affiliate();
69 if ( ! empty( $affiliate_id ) ) {
70 $url = str_replace( array( 'http://', 'https://' ), '', $url );
71 $url = 'http://www.shareasale.com/r.cfm?u=' . absint( $affiliate_id ) . '&b=841990&m=64739&afftrack=plugin&urllink=' . urlencode( $url );
72 }
73
74 return $url;
75 }
76
77 public static function get_affiliate() {
78 return absint( apply_filters( 'frm_affiliate_id', 0 ) );
79 }
80
81 /**
82 * @since 3.04.02
83 * @param array|string $args
84 * @param string $page
85 */
86 public static function admin_upgrade_link( $args, $page = '' ) {
87 if ( empty( $page ) ) {
88 $page = 'https://formidableforms.com/lite-upgrade/';
89 } else {
90 $page = str_replace( 'https://formidableforms.com/', '', $page );
91 $page = 'https://formidableforms.com/' . $page;
92 }
93
94 $anchor = '';
95 if ( is_array( $args ) ) {
96 $medium = $args['medium'];
97 if ( isset( $args['content'] ) ) {
98 $content = $args['content'];
99 }
100 if ( isset( $args['anchor'] ) ) {
101 $anchor = '#' . $args['anchor'];
102 }
103 } else {
104 $medium = $args;
105 }
106
107 $query_args = array(
108 'utm_source' => 'WordPress',
109 'utm_medium' => $medium,
110 'utm_campaign' => 'liteplugin',
111 );
112
113 if ( isset( $content ) ) {
114 $query_args['utm_content'] = $content;
115 }
116
117 if ( is_array( $args ) && isset( $args['param'] ) ) {
118 $query_args['f'] = $args['param'];
119 }
120
121 if ( is_array( $args ) && ! empty( $args['plan'] ) ) {
122 $query_args['plan'] = $args['plan'];
123 }
124
125 $link = add_query_arg( $query_args, $page ) . $anchor;
126 return self::make_affiliate_url( $link );
127 }
128
129 /**
130 * Get the Formidable settings
131 *
132 * @since 2.0
133 *
134 * @param array $args - May include the form id when values need translation.
135 * @return FrmSettings $frm_setings
136 */
137 public static function get_settings( $args = array() ) {
138 global $frm_settings;
139 if ( empty( $frm_settings ) ) {
140 $frm_settings = new FrmSettings( $args );
141 } elseif ( isset( $args['current_form'] ) ) {
142 // If the global has already been set, allow strings to be filtered.
143 $frm_settings->maybe_filter_for_form( $args );
144 }
145
146 return $frm_settings;
147 }
148
149 public static function get_menu_name() {
150 $frm_settings = self::get_settings();
151
152 return $frm_settings->menu;
153 }
154
155 /**
156 * @since 3.05
157 */
158 public static function svg_logo( $atts = array() ) {
159 $defaults = array(
160 'height' => 18,
161 'width' => 18,
162 'fill' => '#4d4d4d',
163 'orange' => '#f05a24',
164 );
165 $atts = array_merge( $defaults, $atts );
166
167 return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 599.68 601.37" width="' . esc_attr( $atts['width'] ) . '" height="' . esc_attr( $atts['height'] ) . '">
168 <path fill="' . esc_attr( $atts['orange'] ) . '" d="M289.6 384h140v76h-140z"/>
169 <path fill="' . esc_attr( $atts['fill'] ) . '" d="M400.2 147h-200c-17 0-30.6 12.2-30.6 29.3V218h260v-71zM397.9 264H169.6v196h75V340H398a32.2 32.2 0 0 0 30.1-21.4 24.3 24.3 0 0 0 1.7-8.7V264zM299.8 601.4A300.3 300.3 0 0 1 0 300.7a299.8 299.8 0 1 1 511.9 212.6 297.4 297.4 0 0 1-212 88zm0-563A262 262 0 0 0 38.3 300.7a261.6 261.6 0 1 0 446.5-185.5 259.5 259.5 0 0 0-185-76.8z"/>
170 </svg>';
171 }
172
173 /**
174 * @since 4.0
175 */
176 public static function show_logo( $atts = array() ) {
177 echo self::kses( self::svg_logo( $atts ), 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
178 }
179
180 /**
181 * @since 4.03.02
182 */
183 public static function show_header_logo() {
184 $icon = self::svg_logo(
185 array(
186 'height' => 35,
187 'width' => 35,
188 )
189 );
190
191 $new_icon = apply_filters( 'frm_icon', $icon, true );
192 if ( $new_icon !== $icon ) {
193 if ( strpos( $new_icon, '<svg' ) === 0 ) {
194 $icon = str_replace( 'viewBox="0 0 20', 'width="30" height="35" style="color:#929699" viewBox="0 0 20', $new_icon );
195 } else {
196 // Show nothing if it isn't an SVG.
197 $icon = '<div style="height:39px"></div>';
198 }
199 }
200 echo self::kses( $icon, 'all' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
201 }
202
203 /**
204 * @since 2.02.04
205 */
206 public static function ips_saved() {
207 $frm_settings = self::get_settings();
208
209 return ! $frm_settings->no_ips;
210 }
211
212 public static function pro_is_installed() {
213 return apply_filters( 'frm_pro_installed', false );
214 }
215
216 /**
217 * @since 4.06.02
218 */
219 public static function pro_is_connected() {
220 global $frm_vars;
221 return self::pro_is_installed() && $frm_vars['pro_is_authorized'];
222 }
223
224 /**
225 * @since 4.06
226 */
227 public static function is_form_builder_page() {
228 $action = self::simple_get( 'frm_action', 'sanitize_title' );
229 return self::is_admin_page( 'formidable' ) && ( $action === 'edit' || $action === 'settings' || $action === 'duplicate' );
230 }
231
232 public static function is_formidable_admin() {
233 $page = self::simple_get( 'page', 'sanitize_title' );
234 $is_formidable = strpos( $page, 'formidable' ) !== false;
235 if ( empty( $page ) ) {
236 $is_formidable = self::is_view_builder_page();
237 }
238
239 return $is_formidable;
240 }
241
242 /**
243 * Check for certain page in Formidable settings
244 *
245 * @since 2.0
246 *
247 * @param string $page The name of the page to check
248 *
249 * @return boolean
250 */
251 public static function is_admin_page( $page = 'formidable' ) {
252 global $pagenow;
253 $get_page = self::simple_get( 'page', 'sanitize_title' );
254 if ( $pagenow ) {
255 // allow this to be true during ajax load i.e. ajax form builder loading
256 $is_page = ( $pagenow == 'admin.php' || $pagenow == 'admin-ajax.php' ) && $get_page == $page;
257 if ( $is_page ) {
258 return true;
259 }
260 }
261
262 return is_admin() && $get_page == $page;
263 }
264
265 /**
266 * If the current page is for editing or creating a view.
267 * Returns false for the views listing page.
268 *
269 * @since 4.0
270 */
271 public static function is_view_builder_page() {
272 global $pagenow;
273
274 if ( $pagenow !== 'post.php' && $pagenow !== 'post-new.php' && $pagenow !== 'edit.php' ) {
275 return false;
276 }
277
278 $post_type = self::simple_get( 'post_type', 'sanitize_title' );
279
280 if ( empty( $post_type ) ) {
281 $post_id = self::simple_get( 'post', 'absint' );
282 $post = get_post( $post_id );
283 $post_type = $post ? $post->post_type : '';
284 }
285
286 return $post_type === 'frm_display';
287 }
288
289 /**
290 * Check for the form preview page
291 *
292 * @since 2.0
293 *
294 * @param None
295 *
296 * @return boolean
297 */
298 public static function is_preview_page() {
299 global $pagenow;
300 $action = self::simple_get( 'action', 'sanitize_title' );
301
302 return $pagenow && $pagenow == 'admin-ajax.php' && $action == 'frm_forms_preview';
303 }
304
305 /**
306 * Check for ajax except the form preview page
307 *
308 * @since 2.0
309 *
310 * @param None
311 *
312 * @return boolean
313 */
314 public static function doing_ajax() {
315 return wp_doing_ajax() && ! self::is_preview_page();
316 }
317
318 public static function js_suffix() {
319 return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
320 }
321
322 /**
323 * @since 2.0.8
324 */
325 public static function prevent_caching() {
326 global $frm_vars;
327
328 return isset( $frm_vars['prevent_caching'] ) && $frm_vars['prevent_caching'];
329 }
330
331 /**
332 * Check if on an admin page
333 *
334 * @since 2.0
335 *
336 * @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 $post_url = self::maybe_full_screen_link( $post_url );
1406
1407 return '<a href="' . esc_url( $post_url ) . '">' . self::truncate( $post->post_title, 50 ) . '</a>';
1408 }
1409
1410 return '';
1411 }
1412
1413 /**
1414 * Hide the WordPress menus on some pages.
1415 *
1416 * @since 4.0
1417 *
1418 * @return bool
1419 */
1420 public static function is_full_screen() {
1421 return self::is_form_builder_page() ||
1422 self::is_admin_page( 'formidable-styles' ) ||
1423 self::is_admin_page( 'formidable-styles2' ) ||
1424 self::simple_get( 'frm-full', 'absint' ) ||
1425 self::is_view_builder_page();
1426 }
1427
1428 /**
1429 * @since 4.0
1430 */
1431 public static function maybe_full_screen_link( $link ) {
1432 $is_full = self::simple_get( 'frm-full', 'absint' );
1433 if ( $is_full && ! empty( $link ) && $link !== '#' ) {
1434 $link .= '&frm-full=1';
1435 }
1436 return $link;
1437 }
1438
1439 /**
1440 * @param string $field_name
1441 * @param string|array $capability
1442 * @param string $multiple 'single' and 'multiple'
1443 */
1444 public static function wp_roles_dropdown( $field_name, $capability, $multiple = 'single' ) {
1445 ?>
1446 <select name="<?php echo esc_attr( $field_name ); ?>" id="<?php echo esc_attr( $field_name ); ?>"
1447 <?php echo ( 'multiple' === $multiple ) ? 'multiple="multiple"' : ''; ?>
1448 class="frm_multiselect">
1449 <?php self::roles_options( $capability ); ?>
1450 </select>
1451 <?php
1452 }
1453
1454 /**
1455 * @since 4.07
1456 * @param array|string $selected
1457 * @param string $current
1458 */
1459 private static function selected( $selected, $current ) {
1460 if ( is_callable( 'FrmProAppHelper::selected' ) ) {
1461 FrmProAppHelper::selected( $selected, $current );
1462 } else {
1463 selected( in_array( $current, (array) $selected, true ) );
1464 }
1465 }
1466
1467 /**
1468 * @param string|array $capability
1469 */
1470 public static function roles_options( $capability ) {
1471 global $frm_vars;
1472 if ( isset( $frm_vars['editable_roles'] ) ) {
1473 $editable_roles = $frm_vars['editable_roles'];
1474 } else {
1475 $editable_roles = get_editable_roles();
1476 $frm_vars['editable_roles'] = $editable_roles;
1477 }
1478
1479 foreach ( $editable_roles as $role => $details ) {
1480 $name = translate_user_role( $details['name'] );
1481 ?>
1482 <option value="<?php echo esc_attr( $role ); ?>" <?php self::selected( $capability, $role ); ?>><?php echo esc_attr( $name ); ?> </option>
1483 <?php
1484 unset( $role, $details );
1485 }
1486 }
1487
1488 /**
1489 * Gets the list of capabilities.
1490 *
1491 * @since 5.0 Parameter `$type` supports `pro_only` value.
1492 *
1493 * @param string $type Supports `auto`, `pro`, or `pro_only`.
1494 * @return array
1495 */
1496 public static function frm_capabilities( $type = 'auto' ) {
1497 if ( ! self::pro_is_installed() && ! in_array( $type, array( 'pro', 'pro_only' ), true ) ) {
1498 return self::get_lite_capabilities();
1499 }
1500
1501 $pro_cap = array(
1502 'frm_create_entries' => __( 'Add Entries from Admin Area', 'formidable' ),
1503 'frm_edit_entries' => __( 'Edit Entries from Admin Area', 'formidable' ),
1504 'frm_view_reports' => __( 'View Reports', 'formidable' ),
1505 'frm_edit_displays' => __( 'Add/Edit Views', 'formidable' ),
1506 );
1507 /**
1508 * @since 5.3.1
1509 *
1510 * @param array<string,string> $pro_cap
1511 */
1512 $pro_cap = apply_filters( 'frm_pro_capabilities', $pro_cap );
1513
1514 if ( 'pro_only' === $type ) {
1515 return $pro_cap;
1516 }
1517
1518 return self::get_lite_capabilities() + $pro_cap;
1519 }
1520
1521 /**
1522 * Get the list of lite plugin capabilities.
1523 *
1524 * @since 5.3.1
1525 *
1526 * @return array<string,string>
1527 */
1528 private static function get_lite_capabilities() {
1529 return array(
1530 'frm_view_forms' => __( 'View Forms', 'formidable' ),
1531 'frm_edit_forms' => __( 'Add and Edit Forms', 'formidable' ),
1532 'frm_delete_forms' => __( 'Delete Forms', 'formidable' ),
1533 'frm_change_settings' => __( 'Access this Settings Page', 'formidable' ),
1534 'frm_view_entries' => __( 'View Entries from Admin Area', 'formidable' ),
1535 'frm_delete_entries' => __( 'Delete Entries from Admin Area', 'formidable' ),
1536 );
1537 }
1538
1539 /**
1540 * Call the WordPress current_user_can but also validate empty strings as true for any logged in user
1541 *
1542 * @since 4.06.03
1543 *
1544 * @param string $role
1545 *
1546 * @return bool
1547 */
1548 public static function current_user_can( $role ) {
1549 if ( $role === '-1' ) {
1550 return false;
1551 }
1552
1553 if ( $role === 'loggedout' ) {
1554 return ! is_user_logged_in();
1555 }
1556
1557 if ( $role === 'loggedin' || ! $role ) {
1558 return is_user_logged_in();
1559 }
1560
1561 if ( $role == 1 ) {
1562 $role = 'administrator';
1563 }
1564
1565 if ( ! is_user_logged_in() ) {
1566 return false;
1567 }
1568
1569 return current_user_can( $role );
1570 }
1571
1572 /**
1573 * @param string|array $needed_role
1574 * @return bool
1575 */
1576 public static function user_has_permission( $needed_role ) {
1577 if ( is_array( $needed_role ) ) {
1578 foreach ( $needed_role as $role ) {
1579 if ( self::current_user_can( $role ) ) {
1580 return true;
1581 }
1582 }
1583
1584 return false;
1585 }
1586
1587 $can = self::current_user_can( $needed_role );
1588
1589 if ( $can || in_array( $needed_role, array( '-1', 'loggedout' ) ) ) {
1590 return $can;
1591 }
1592
1593 $roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
1594 foreach ( $roles as $role ) {
1595 if ( current_user_can( $role ) ) {
1596 return true;
1597 }
1598 if ( $role == $needed_role ) {
1599 break;
1600 }
1601 }
1602
1603 return false;
1604 }
1605
1606 /**
1607 * Make sure administrators can see Formidable menu
1608 *
1609 * @since 2.0
1610 */
1611 public static function maybe_add_permissions() {
1612 self::force_capability( 'frm_view_entries' );
1613
1614 if ( ! current_user_can( 'administrator' ) || current_user_can( 'frm_view_forms' ) ) {
1615 return;
1616 }
1617
1618 $user_id = get_current_user_id();
1619 $user = new WP_User( $user_id );
1620 $frm_roles = self::frm_capabilities();
1621 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1622 $user->add_cap( $frm_role );
1623 unset( $frm_role, $frm_role_description );
1624 }
1625 }
1626
1627 /**
1628 * Make sure admins have permission to see the menu items
1629 *
1630 * @since 2.0.6
1631 */
1632 public static function force_capability( $cap = 'frm_change_settings' ) {
1633 if ( current_user_can( 'administrator' ) && ! current_user_can( $cap ) ) {
1634 $role = get_role( 'administrator' );
1635 $frm_roles = self::frm_capabilities();
1636 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
1637 $role->add_cap( $frm_role );
1638 }
1639 }
1640 }
1641
1642 /**
1643 * Check if the user has permision for action.
1644 * Return permission message and stop the action if no permission
1645 *
1646 * @since 2.0
1647 *
1648 * @param string $permission
1649 */
1650 public static function permission_check( $permission, $show_message = 'show' ) {
1651 $permission_error = self::permission_nonce_error( $permission );
1652 if ( $permission_error !== false ) {
1653 if ( 'hide' == $show_message ) {
1654 $permission_error = '';
1655 }
1656 wp_die( esc_html( $permission_error ) );
1657 }
1658 }
1659
1660 /**
1661 * Check user permission and nonce
1662 *
1663 * @since 2.0
1664 *
1665 * @param string $permission
1666 *
1667 * @return false|string The permission message or false if allowed
1668 */
1669 public static function permission_nonce_error( $permission, $nonce_name = '', $nonce = '' ) {
1670 if ( ! empty( $permission ) && ! current_user_can( $permission ) && ! current_user_can( 'administrator' ) ) {
1671 $frm_settings = self::get_settings();
1672
1673 return $frm_settings->admin_permission;
1674 }
1675
1676 $error = false;
1677 if ( empty( $nonce_name ) ) {
1678 return $error;
1679 }
1680
1681 $nonce_value = ( $_REQUEST && isset( $_REQUEST[ $nonce_name ] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $nonce_name ] ) ) : '';
1682 if ( $_REQUEST && ( ! isset( $_REQUEST[ $nonce_name ] ) || ! wp_verify_nonce( $nonce_value, $nonce ) ) ) {
1683 $frm_settings = self::get_settings();
1684 $error = $frm_settings->admin_permission;
1685 }
1686
1687 return $error;
1688 }
1689
1690 public static function checked( $values, $current ) {
1691 if ( self::check_selected( $values, $current ) ) {
1692 echo ' checked="checked"';
1693 }
1694 }
1695
1696 public static function check_selected( $values, $current ) {
1697 $values = self::recursive_function_map( $values, 'trim' );
1698 $values = self::recursive_function_map( $values, 'htmlspecialchars_decode' );
1699 $current = htmlspecialchars_decode( trim( $current ) );
1700
1701 return ( is_array( $values ) && in_array( $current, $values ) ) || ( ! is_array( $values ) && $values == $current );
1702 }
1703
1704 public static function recursive_function_map( $value, $function ) {
1705 if ( is_array( $value ) ) {
1706 $original_function = $function;
1707 if ( count( $value ) ) {
1708 $function = explode( ', ', FrmDb::prepare_array_values( $value, $function ) );
1709 } else {
1710 $function = array( $function );
1711 }
1712 if ( ! self::is_assoc( $value ) ) {
1713 $value = array_map( array( 'FrmAppHelper', 'recursive_function_map' ), $value, $function );
1714 } else {
1715 foreach ( $value as $k => $v ) {
1716 if ( ! is_array( $v ) ) {
1717 $value[ $k ] = call_user_func( $original_function, $v );
1718 }
1719 }
1720 }
1721 } else {
1722 $value = call_user_func( $function, $value );
1723 }
1724
1725 return $value;
1726 }
1727
1728 public static function is_assoc( $array ) {
1729 return (bool) count( array_filter( array_keys( $array ), 'is_string' ) );
1730 }
1731
1732 /**
1733 * Flatten a multi-dimensional array
1734 */
1735 public static function array_flatten( $array, $keys = 'keep' ) {
1736 $return = array();
1737 foreach ( $array as $key => $value ) {
1738 if ( is_array( $value ) ) {
1739 $return = array_merge( $return, self::array_flatten( $value, $keys ) );
1740 } else {
1741 if ( $keys == 'keep' ) {
1742 $return[ $key ] = $value;
1743 } else {
1744 $return[] = $value;
1745 }
1746 }
1747 }
1748
1749 return $return;
1750 }
1751
1752 public static function esc_textarea( $text, $is_rich_text = false ) {
1753 $safe_text = str_replace( '&quot;', '"', $text );
1754 if ( ! $is_rich_text ) {
1755 $safe_text = htmlspecialchars( $safe_text, ENT_NOQUOTES );
1756 }
1757 $safe_text = str_replace( '&amp; ', '& ', $safe_text );
1758
1759 return apply_filters( 'esc_textarea', $safe_text, $text );
1760 }
1761
1762 /**
1763 * Add auto paragraphs to text areas
1764 *
1765 * @since 2.0
1766 */
1767 public static function use_wpautop( $content ) {
1768 if ( apply_filters( 'frm_use_wpautop', true ) && ! is_array( $content ) ) {
1769 $content = wpautop( str_replace( '<br>', '<br />', $content ) );
1770 }
1771
1772 return $content;
1773 }
1774
1775 public static function replace_quotes( $val ) {
1776 // Replace double quotes.
1777 $val = str_replace( array( '&#8220;', '&#8221;', '&#8243;' ), '"', $val );
1778
1779 // Replace single quotes.
1780 $val = str_replace( array( '&#8216;', '&#8217;', '&#8242;', '&prime;', '&rsquo;', '&lsquo;' ), "'", $val );
1781
1782 return $val;
1783 }
1784
1785 /**
1786 * @param string $handle
1787 */
1788 public static function script_version( $handle, $default = 0 ) {
1789 global $wp_scripts;
1790 if ( ! $wp_scripts ) {
1791 return $default;
1792 }
1793
1794 $ver = $default;
1795 if ( ! isset( $wp_scripts->registered[ $handle ] ) ) {
1796 return $ver;
1797 }
1798
1799 $query = $wp_scripts->registered[ $handle ];
1800 if ( is_object( $query ) && ! empty( $query->ver ) ) {
1801 $ver = $query->ver;
1802 }
1803
1804 return $ver;
1805 }
1806
1807 /**
1808 * @since 5.0.13 added $echo param.
1809 *
1810 * @param string $url
1811 * @param bool $echo
1812 * @return string|void
1813 */
1814 public static function js_redirect( $url, $echo = false ) {
1815 $callback = function() use ( $url ) {
1816 echo '<script type="text/javascript">window.location="' . esc_url_raw( $url ) . '"</script>';
1817 };
1818 return self::clip( $callback, $echo );
1819 }
1820
1821 public static function get_user_id_param( $user_id ) {
1822 if ( ! $user_id || is_numeric( $user_id ) ) {
1823 return $user_id;
1824 }
1825
1826 $user_id = sanitize_text_field( $user_id );
1827 if ( $user_id == 'current' ) {
1828 $user_id = get_current_user_id();
1829 } else {
1830 if ( is_email( $user_id ) ) {
1831 $user = get_user_by( 'email', $user_id );
1832 } else {
1833 $user = get_user_by( 'login', $user_id );
1834 }
1835
1836 if ( $user ) {
1837 $user_id = $user->ID;
1838 }
1839 unset( $user );
1840 }
1841
1842 return $user_id;
1843 }
1844
1845 public static function get_file_contents( $filename, $atts = array() ) {
1846 if ( ! is_file( $filename ) ) {
1847 return false;
1848 }
1849
1850 extract( $atts ); // phpcs:ignore WordPress.PHP.DontExtract
1851 ob_start();
1852 include( $filename );
1853 $contents = ob_get_contents();
1854 ob_end_clean();
1855
1856 return $contents;
1857 }
1858
1859 /**
1860 * @param string $name
1861 * @param string $table_name
1862 * @param string $column
1863 * @param int $id
1864 * @param int $num_chars
1865 */
1866 public static function get_unique_key( $name, $table_name, $column, $id = 0, $num_chars = 5 ) {
1867 $key = '';
1868 if ( $name ) {
1869 $key = sanitize_key( $name );
1870 $key = self::maybe_clear_long_key( $key, $column );
1871 }
1872
1873 if ( ! $key ) {
1874 $key = self::generate_new_key( $num_chars );
1875 }
1876
1877 $key = self::prevent_numeric_and_reserved_keys( $key );
1878
1879 $similar_keys = FrmDb::get_col(
1880 $table_name,
1881 array(
1882 $column . ' like%' => $key,
1883 'ID !' => $id,
1884 ),
1885 $column
1886 );
1887
1888 // Create a unique field id if it has already been used.
1889 if ( in_array( $key, $similar_keys, true ) ) {
1890 $key = self::maybe_truncate_key_before_appending( $column, $key );
1891
1892 /**
1893 * Allow for a custom separator between the attempted key and the generated suffix.
1894 *
1895 * @since 5.2.03
1896 *
1897 * @param string $separator. Default empty.
1898 * @param string $key the key without the added suffix.
1899 */
1900 $separator = apply_filters( 'frm_unique_' . $column . '_separator', '', $key );
1901
1902 $suffix = 2;
1903 do {
1904 $key_check = $key . $separator . $suffix;
1905 ++$suffix;
1906 } while ( in_array( $key_check, $similar_keys, true ) );
1907
1908 $key = $key_check;
1909 }
1910
1911 return $key;
1912 }
1913
1914 /**
1915 * Avoid trying to append to a really long key,
1916 * The database limit is 100 for form and field keys so we want to avoid getting too close.
1917 *
1918 * @param string $column
1919 * @param string $key
1920 * @return string
1921 */
1922 private static function maybe_truncate_key_before_appending( $column, $key ) {
1923 if ( in_array( $column, array( 'form_key', 'field_key' ), true ) ) {
1924 $max_key_length_before_truncating = 60;
1925 if ( strlen( $key ) > $max_key_length_before_truncating ) {
1926 $key = substr( $key, 0, $max_key_length_before_truncating );
1927 if ( is_numeric( $key ) ) {
1928 $key .= 'a';
1929 }
1930 }
1931 }
1932 return $key;
1933 }
1934
1935 /**
1936 * Possibly reset a key to avoid conflicts with column size limits.
1937 *
1938 * @param string $key
1939 * @param string $column
1940 * @return string either the original key value, or an empty string if the key was too long.
1941 */
1942 private static function maybe_clear_long_key( $key, $column ) {
1943 if ( 'field_key' === $column && strlen( $key ) >= 70 ) {
1944 $key = '';
1945 }
1946 return $key;
1947 }
1948
1949 /**
1950 * @param int $num_chars
1951 * @return string
1952 */
1953 private static function generate_new_key( $num_chars ) {
1954 $max_slug_value = pow( 36, $num_chars );
1955 $min_slug_value = 37; // we want to have at least 2 characters in the slug
1956 return base_convert( rand( $min_slug_value, $max_slug_value ), 10, 36 );
1957 }
1958
1959 /**
1960 * @param string $key
1961 * @return string
1962 */
1963 private static function prevent_numeric_and_reserved_keys( $key ) {
1964 if ( is_numeric( $key ) ) {
1965 $key .= 'a';
1966 } else {
1967 $not_allowed = array(
1968 'id',
1969 'key',
1970 'created-at',
1971 'detaillink',
1972 'editlink',
1973 'siteurl',
1974 'evenodd',
1975 );
1976 if ( in_array( $key, $not_allowed, true ) ) {
1977 $key .= 'a';
1978 }
1979 }
1980 return $key;
1981 }
1982
1983 /**
1984 * Editing a Form or Entry
1985 *
1986 * @param string $table
1987 *
1988 * @return bool|array
1989 */
1990 public static function setup_edit_vars( $record, $table, $fields = '', $default = false, $post_values = array(), $args = array() ) {
1991 if ( ! $record ) {
1992 return false;
1993 }
1994
1995 if ( empty( $post_values ) ) {
1996 $post_values = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
1997 }
1998
1999 $values = array(
2000 'id' => $record->id,
2001 'fields' => array(),
2002 );
2003
2004 foreach ( array( 'name', 'description' ) as $var ) {
2005 $default_val = isset( $record->{$var} ) ? $record->{$var} : '';
2006 $values[ $var ] = self::get_param( $var, $default_val, 'get', 'wp_kses_post' );
2007 unset( $var, $default_val );
2008 }
2009
2010 $values['description'] = self::use_wpautop( $values['description'] );
2011
2012 self::fill_form_opts( $record, $table, $post_values, $values );
2013
2014 self::prepare_field_arrays( $fields, $record, $values, array_merge( $args, compact( 'default', 'post_values' ) ) );
2015
2016 if ( $table === 'entries' ) {
2017 $values = FrmEntriesHelper::setup_edit_vars( $values, $record );
2018 } elseif ( $table === 'forms' ) {
2019 $values = FrmFormsHelper::setup_edit_vars( $values, $record, $post_values );
2020 }
2021
2022 return $values;
2023 }
2024
2025 private static function prepare_field_arrays( $fields, $record, array &$values, $args ) {
2026 if ( ! empty( $fields ) ) {
2027 foreach ( (array) $fields as $field ) {
2028 if ( ! self::is_admin_page() ) {
2029 // Don't prep default values on the form settings page.
2030 $field->default_value = apply_filters( 'frm_get_default_value', $field->default_value, $field, true );
2031 }
2032 $args['parent_form_id'] = isset( $args['parent_form_id'] ) ? $args['parent_form_id'] : $field->form_id;
2033 self::fill_field_defaults( $field, $record, $values, $args );
2034 }
2035 }
2036 }
2037
2038 private static function fill_field_defaults( $field, $record, array &$values, $args ) {
2039 $post_values = $args['post_values'];
2040
2041 if ( $args['default'] ) {
2042 $meta_value = $field->default_value;
2043 } else {
2044 if ( $record->post_id && self::pro_is_installed() && isset( $field->field_options['post_field'] ) && $field->field_options['post_field'] ) {
2045 if ( ! isset( $field->field_options['custom_field'] ) ) {
2046 $field->field_options['custom_field'] = '';
2047 }
2048 $meta_value = FrmProEntryMetaHelper::get_post_value(
2049 $record->post_id,
2050 $field->field_options['post_field'],
2051 $field->field_options['custom_field'],
2052 array(
2053 'truncate' => false,
2054 'type' => $field->type,
2055 'form_id' => $field->form_id,
2056 'field' => $field,
2057 )
2058 );
2059 } else {
2060 $meta_value = FrmEntryMeta::get_meta_value( $record, $field->id );
2061 }
2062 }
2063
2064 $field_type = isset( $post_values['field_options'][ 'type_' . $field->id ] ) ? $post_values['field_options'][ 'type_' . $field->id ] : $field->type;
2065 if ( isset( $post_values['item_meta'][ $field->id ] ) ) {
2066 $new_value = $post_values['item_meta'][ $field->id ];
2067 self::unserialize_or_decode( $new_value );
2068 } else {
2069 $new_value = $meta_value;
2070 }
2071
2072 $field_array = self::start_field_array( $field );
2073 $field_array['value'] = $new_value;
2074 $field_array['type'] = apply_filters( 'frm_field_type', $field_type, $field, $new_value );
2075 $field_array['parent_form_id'] = $args['parent_form_id'];
2076
2077 $args['field_type'] = $field_type;
2078
2079 FrmFieldsHelper::prepare_edit_front_field( $field_array, $field, $values['id'], $args );
2080
2081 if ( ! isset( $field_array['unique'] ) || ! $field_array['unique'] ) {
2082 $field_array['unique_msg'] = '';
2083 }
2084
2085 $field_array = array_merge( (array) $field->field_options, $field_array );
2086
2087 $values['fields'][ $field->id ] = $field_array;
2088 }
2089
2090 /**
2091 * @since 3.0
2092 *
2093 * @param object $field
2094 *
2095 * @return array
2096 */
2097 public static function start_field_array( $field ) {
2098 return array(
2099 'id' => $field->id,
2100 'default_value' => $field->default_value,
2101 'name' => $field->name,
2102 'description' => $field->description,
2103 'options' => $field->options,
2104 'required' => $field->required,
2105 'field_key' => $field->field_key,
2106 'field_order' => $field->field_order,
2107 'form_id' => $field->form_id,
2108 );
2109 }
2110
2111 /**
2112 * @param string $table
2113 */
2114 private static function fill_form_opts( $record, $table, $post_values, array &$values ) {
2115 if ( $table == 'entries' ) {
2116 $form = $record->form_id;
2117 FrmForm::maybe_get_form( $form );
2118 } else {
2119 $form = $record;
2120 }
2121
2122 if ( ! $form ) {
2123 return;
2124 }
2125
2126 $values['form_name'] = isset( $record->form_id ) ? $form->name : '';
2127 $values['parent_form_id'] = isset( $record->form_id ) ? $form->parent_form_id : 0;
2128
2129 if ( ! is_array( $form->options ) ) {
2130 return;
2131 }
2132
2133 foreach ( $form->options as $opt => $value ) {
2134 if ( isset( $post_values[ $opt ] ) ) {
2135 $values[ $opt ] = $post_values[ $opt ];
2136 self::unserialize_or_decode( $values[ $opt ] );
2137 } else {
2138 $values[ $opt ] = $value;
2139 }
2140 }
2141
2142 self::fill_form_defaults( $post_values, $values );
2143 }
2144
2145 /**
2146 * Set to POST value or default
2147 */
2148 private static function fill_form_defaults( $post_values, array &$values ) {
2149 $form_defaults = FrmFormsHelper::get_default_opts();
2150
2151 foreach ( $form_defaults as $opt => $default ) {
2152 if ( ! isset( $values[ $opt ] ) || $values[ $opt ] == '' ) {
2153 $values[ $opt ] = ( $post_values && isset( $post_values['options'][ $opt ] ) ) ? $post_values['options'][ $opt ] : $default;
2154 }
2155
2156 unset( $opt, $default );
2157 }
2158
2159 if ( ! isset( $values['custom_style'] ) ) {
2160 $values['custom_style'] = self::custom_style_value( $post_values );
2161 }
2162
2163 foreach ( array( 'before', 'after', 'submit' ) as $h ) {
2164 if ( ! isset( $values[ $h . '_html' ] ) ) {
2165 $values[ $h . '_html' ] = ( isset( $post_values['options'][ $h . '_html' ] ) ? $post_values['options'][ $h . '_html' ] : FrmFormsHelper::get_default_html( $h ) );
2166 }
2167 unset( $h );
2168 }
2169 }
2170
2171 /**
2172 * @since 2.2.10
2173 *
2174 * @param array $post_values
2175 *
2176 * @return boolean|int
2177 */
2178 public static function custom_style_value( $post_values ) {
2179 if ( ! empty( $post_values ) && isset( $post_values['options']['custom_style'] ) ) {
2180 $custom_style = absint( $post_values['options']['custom_style'] );
2181 } else {
2182 $frm_settings = self::get_settings();
2183 $custom_style = ( $frm_settings->load_style != 'none' );
2184 }
2185
2186 return $custom_style;
2187 }
2188
2189 public static function truncate( $str, $length, $minword = 3, $continue = '...' ) {
2190 if ( is_array( $str ) ) {
2191 return '';
2192 }
2193
2194 $length = (int) $length;
2195 $str = wp_strip_all_tags( $str );
2196 $original_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $str ) );
2197
2198 if ( $length == 0 ) {
2199 return '';
2200 } elseif ( $length <= 10 ) {
2201 $sub = self::mb_function( array( 'mb_substr', 'substr' ), array( $str, 0, $length ) );
2202
2203 return $sub . ( ( $length < $original_len ) ? $continue : '' );
2204 }
2205
2206 $sub = '';
2207 $len = 0;
2208
2209 $words = self::mb_function( array( 'mb_split', 'explode' ), array( ' ', $str ) );
2210
2211 foreach ( $words as $word ) {
2212 $part = ( ( $sub != '' ) ? ' ' : '' ) . $word;
2213 $total_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub . $part ) );
2214 if ( $total_len > $length && substr_count( $sub, ' ' ) ) {
2215 break;
2216 }
2217
2218 $sub .= $part;
2219 $len += self::mb_function( array( 'mb_strlen', 'strlen' ), array( $part ) );
2220
2221 if ( substr_count( $sub, ' ' ) > $minword && $total_len >= $length ) {
2222 break;
2223 }
2224
2225 unset( $total_len, $word );
2226 }
2227
2228 return $sub . ( ( $len < $original_len ) ? $continue : '' );
2229 }
2230
2231 public static function mb_function( $function_names, $args ) {
2232 $mb_function_name = $function_names[0];
2233 $function_name = $function_names[1];
2234 if ( function_exists( $mb_function_name ) ) {
2235 $function_name = $mb_function_name;
2236 }
2237
2238 return call_user_func_array( $function_name, $args );
2239 }
2240
2241 public static function get_formatted_time( $date, $date_format = '', $time_format = '' ) {
2242 if ( empty( $date ) ) {
2243 return $date;
2244 }
2245
2246 if ( empty( $date_format ) ) {
2247 $date_format = get_option( 'date_format' );
2248 }
2249
2250 if ( preg_match( '/^\d{1-2}\/\d{1-2}\/\d{4}$/', $date ) && self::pro_is_installed() ) {
2251 $frmpro_settings = new FrmProSettings();
2252 $date = FrmProAppHelper::convert_date( $date, $frmpro_settings->date_format, 'Y-m-d' );
2253 }
2254
2255 $formatted = self::get_localized_date( $date_format, $date );
2256
2257 $do_time = ( gmdate( 'H:i:s', strtotime( $date ) ) != '00:00:00' );
2258 if ( $do_time ) {
2259 $formatted .= self::add_time_to_date( $time_format, $date );
2260 }
2261
2262 return $formatted;
2263 }
2264
2265 private static function add_time_to_date( $time_format, $date ) {
2266 if ( empty( $time_format ) ) {
2267 $time_format = get_option( 'time_format' );
2268 }
2269
2270 $trimmed_format = trim( $time_format );
2271 $time = '';
2272 if ( $time_format && ! empty( $trimmed_format ) ) {
2273 $time = ' ' . __( 'at', 'formidable' ) . ' ' . self::get_localized_date( $time_format, $date );
2274 }
2275
2276 return $time;
2277 }
2278
2279 /**
2280 * @since 2.0.8
2281 */
2282 public static function get_localized_date( $date_format, $date ) {
2283 $date = get_date_from_gmt( $date );
2284
2285 return date_i18n( $date_format, strtotime( $date ) );
2286 }
2287
2288 /**
2289 * Gets the time ago in words
2290 *
2291 * @param int $from in seconds
2292 * @param int|string $to in seconds
2293 *
2294 * @return string $time_ago
2295 */
2296 public static function human_time_diff( $from, $to = '', $levels = 1 ) {
2297 if ( empty( $to ) ) {
2298 $now = new DateTime();
2299 } else {
2300 $now = new DateTime( '@' . $to );
2301 }
2302 $ago = new DateTime( '@' . $from );
2303
2304 // Get the time difference
2305 $diff_object = $now->diff( $ago );
2306 $diff = get_object_vars( $diff_object );
2307
2308 // Add week amount and update day amount
2309 $diff['w'] = floor( $diff['d'] / 7 );
2310 $diff['d'] -= $diff['w'] * 7;
2311
2312 $time_strings = self::get_time_strings();
2313
2314 if ( ! is_numeric( $levels ) ) {
2315 // Show time in specified unit.
2316 $levels = self::get_unit( $levels );
2317 if ( isset( $time_strings[ $levels ] ) ) {
2318 $diff = array(
2319 $levels => self::time_format( $levels, $diff ),
2320 );
2321 $time_strings = array(
2322 $levels => $time_strings[ $levels ],
2323 );
2324 }
2325 $levels = 1;
2326 }
2327
2328 foreach ( $time_strings as $k => $v ) {
2329 if ( isset( $diff[ $k ] ) && $diff[ $k ] ) {
2330 $time_strings[ $k ] = $diff[ $k ] . ' ' . ( $diff[ $k ] > 1 ? $v[1] : $v[0] );
2331 } elseif ( isset( $diff[ $k ] ) && count( $time_strings ) === 1 ) {
2332 // Account for 0.
2333 $time_strings[ $k ] = $diff[ $k ] . ' ' . $v[1];
2334 } else {
2335 unset( $time_strings[ $k ] );
2336 }
2337 }
2338
2339 $levels_deep = apply_filters( 'frm_time_ago_levels', $levels, compact( 'time_strings', 'from', 'to' ) );
2340 $time_strings = array_slice( $time_strings, 0, absint( $levels_deep ) );
2341 $time_ago_string = implode( ' ', $time_strings );
2342
2343 return $time_ago_string;
2344 }
2345
2346 /**
2347 * @since 4.05.01
2348 */
2349 private static function time_format( $unit, $diff ) {
2350 $return = array(
2351 'y' => 'y',
2352 'd' => 'days',
2353 );
2354 if ( isset( $return[ $unit ] ) ) {
2355 return $diff[ $return[ $unit ] ];
2356 }
2357
2358 $total = $diff['days'] * self::convert_time( 'd', $unit );
2359
2360 $times = array( 'h', 'i', 's' );
2361
2362 foreach ( $times as $time ) {
2363 if ( ! isset( $diff[ $time ] ) ) {
2364 continue;
2365 }
2366
2367 $total += $diff[ $time ] * self::convert_time( $time, $unit );
2368 }
2369
2370 return floor( $total );
2371 }
2372
2373 /**
2374 * @since 4.05.01
2375 */
2376 private static function convert_time( $from, $to ) {
2377 $convert = array(
2378 's' => 1,
2379 'i' => MINUTE_IN_SECONDS,
2380 'h' => HOUR_IN_SECONDS,
2381 'd' => DAY_IN_SECONDS,
2382 'w' => WEEK_IN_SECONDS,
2383 'm' => DAY_IN_SECONDS * 30.42,
2384 'y' => DAY_IN_SECONDS * 365.25,
2385 );
2386
2387 return $convert[ $from ] / $convert[ $to ];
2388 }
2389
2390 /**
2391 * @since 4.05.01
2392 */
2393 private static function get_unit( $unit ) {
2394 $units = self::get_time_strings();
2395 if ( isset( $units[ $unit ] ) || is_numeric( $unit ) ) {
2396 return $unit;
2397 }
2398
2399 foreach ( $units as $u => $strings ) {
2400 if ( in_array( $unit, $strings ) ) {
2401 return $u;
2402 }
2403 }
2404 return 1;
2405 }
2406
2407 /**
2408 * Get the translatable time strings. The untranslated version is a failsafe
2409 * in case langauges are changing for the unit set in the shortcode.
2410 *
2411 * @since 2.0.20
2412 * @return array
2413 */
2414 private static function get_time_strings() {
2415 return array(
2416 'y' => array(
2417 __( 'year', 'formidable' ),
2418 __( 'years', 'formidable' ),
2419 'year',
2420 ),
2421 'm' => array(
2422 __( 'month', 'formidable' ),
2423 __( 'months', 'formidable' ),
2424 'month',
2425 ),
2426 'w' => array(
2427 __( 'week', 'formidable' ),
2428 __( 'weeks', 'formidable' ),
2429 'week',
2430 ),
2431 'd' => array(
2432 __( 'day', 'formidable' ),
2433 __( 'days', 'formidable' ),
2434 'day',
2435 ),
2436 'h' => array(
2437 __( 'hour', 'formidable' ),
2438 __( 'hours', 'formidable' ),
2439 'hour',
2440 ),
2441 'i' => array(
2442 __( 'minute', 'formidable' ),
2443 __( 'minutes', 'formidable' ),
2444 'minute',
2445 ),
2446 's' => array(
2447 __( 'second', 'formidable' ),
2448 __( 'seconds', 'formidable' ),
2449 'second',
2450 ),
2451 );
2452 }
2453
2454 // Pagination Methods.
2455
2456 /**
2457 * @param integer $current_p
2458 */
2459 public static function get_last_record_num( $r_count, $current_p, $p_size ) {
2460 return ( ( $r_count < ( $current_p * $p_size ) ) ? $r_count : ( $current_p * $p_size ) );
2461 }
2462
2463 /**
2464 * @param integer $current_p
2465 */
2466 public static function get_first_record_num( $r_count, $current_p, $p_size ) {
2467 if ( $current_p == 1 ) {
2468 return 1;
2469 } else {
2470 return ( self::get_last_record_num( $r_count, ( $current_p - 1 ), $p_size ) + 1 );
2471 }
2472 }
2473
2474 /**
2475 * @return array
2476 */
2477 public static function json_to_array( $json_vars ) {
2478 $vars = array();
2479 foreach ( $json_vars as $jv ) {
2480 $jv_name = explode( '[', $jv['name'] );
2481 $last = count( $jv_name ) - 1;
2482 foreach ( $jv_name as $p => $n ) {
2483 $name = trim( $n, ']' );
2484 if ( ! isset( $l1 ) ) {
2485 $l1 = $name;
2486 }
2487
2488 if ( ! isset( $l2 ) ) {
2489 $l2 = $name;
2490 }
2491
2492 if ( ! isset( $l3 ) ) {
2493 $l3 = $name;
2494 }
2495
2496 $this_val = ( $p == $last ) ? $jv['value'] : array();
2497
2498 switch ( $p ) {
2499 case 0:
2500 $l1 = $name;
2501 self::add_value_to_array( $name, $l1, $this_val, $vars );
2502 break;
2503
2504 case 1:
2505 $l2 = $name;
2506 self::add_value_to_array( $name, $l2, $this_val, $vars[ $l1 ] );
2507 break;
2508
2509 case 2:
2510 $l3 = $name;
2511 self::add_value_to_array( $name, $l3, $this_val, $vars[ $l1 ][ $l2 ] );
2512 break;
2513
2514 case 3:
2515 $l4 = $name;
2516 self::add_value_to_array( $name, $l4, $this_val, $vars[ $l1 ][ $l2 ][ $l3 ] );
2517 }
2518
2519 unset( $this_val, $n );
2520 }
2521
2522 unset( $last, $jv );
2523 }
2524
2525 return $vars;
2526 }
2527
2528 /**
2529 * @param string $name
2530 * @param string $l1
2531 */
2532 public static function add_value_to_array( $name, $l1, $val, &$vars ) {
2533 if ( $name == '' ) {
2534 $vars[] = $val;
2535 } elseif ( ! isset( $vars[ $l1 ] ) ) {
2536 $vars[ $l1 ] = $val;
2537 }
2538 }
2539
2540 public static function maybe_add_tooltip( $name, $class = 'closed', $form_name = '' ) {
2541 $tooltips = array(
2542 'action_title' => __( 'Give this action a label for easy reference.', 'formidable' ),
2543 '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' ),
2544 'cc' => __( 'Add CC addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2545 'bcc' => __( 'Add BCC addresses separated by a ",". FORMAT: Name <name@email.com> or name@email.com.', 'formidable' ),
2546 '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' ),
2547 'from' => __( 'Enter the name and/or email address of the sender. FORMAT: John Bates <john@example.com> or john@example.com.', 'formidable' ),
2548 /* translators: %1$s: Form name, %2$s: Date */
2549 '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() ) ),
2550 );
2551
2552 if ( ! isset( $tooltips[ $name ] ) ) {
2553 return;
2554 }
2555
2556 if ( 'open' == $class ) {
2557 echo ' frm_help"';
2558 } else {
2559 echo ' class="frm_help"';
2560 }
2561
2562 echo ' title="' . esc_attr( $tooltips[ $name ] );
2563
2564 if ( 'open' != $class ) {
2565 echo '"';
2566 }
2567 }
2568
2569 /**
2570 * Add the current_page class to that page in the form nav
2571 */
2572 public static function select_current_page( $page, $current_page, $action = array() ) {
2573 if ( $current_page != $page ) {
2574 return;
2575 }
2576
2577 $frm_action = self::simple_get( 'frm_action', 'sanitize_title' );
2578 if ( 'lite-reports' === $frm_action ) {
2579 $frm_action = 'reports';
2580 }
2581
2582 if ( empty( $action ) || ( ! empty( $frm_action ) && in_array( $frm_action, $action ) ) ) {
2583 echo ' class="current_page"';
2584 }
2585 }
2586
2587 /**
2588 * Prepare and json_encode post content
2589 *
2590 * @since 2.0
2591 *
2592 * @param array $post_content
2593 *
2594 * @return string $post_content ( json encoded array )
2595 */
2596 public static function prepare_and_encode( $post_content ) {
2597 // Loop through array to strip slashes and add only the needed ones.
2598 foreach ( $post_content as $key => $val ) {
2599 // Replace problematic characters (like &quot;)
2600 if ( is_string( $val ) ) {
2601 $val = str_replace( '&quot;', '"', $val );
2602 }
2603
2604 self::prepare_action_slashes( $val, $key, $post_content );
2605 unset( $key, $val );
2606 }
2607
2608 // json_encode the array.
2609 $post_content = json_encode( $post_content );
2610
2611 // Add extra slashes for \r\n since WP strips them.
2612 $post_content = str_replace( array( '\\r', '\\n', '\\u', '\\t' ), array( '\\\\r', '\\\\n', '\\\\u', '\\\\t' ), $post_content );
2613
2614 // allow for &quot
2615 $post_content = str_replace( '&quot;', '\\"', $post_content );
2616
2617 return $post_content;
2618 }
2619
2620 private static function prepare_action_slashes( $val, $key, &$post_content ) {
2621 if ( ! isset( $post_content[ $key ] ) || is_numeric( $val ) ) {
2622 return;
2623 }
2624
2625 if ( is_array( $val ) ) {
2626 foreach ( $val as $k1 => $v1 ) {
2627 self::prepare_action_slashes( $v1, $k1, $post_content[ $key ] );
2628 unset( $k1, $v1 );
2629 }
2630 } else {
2631 // Strip all slashes so everything is the same, no matter where the value is coming from
2632 $val = stripslashes( $val );
2633
2634 // Add backslashes before double quotes and forward slashes only
2635 $post_content[ $key ] = addcslashes( $val, '"\\/' );
2636 }
2637 }
2638
2639 /**
2640 * Check for either json or serilized data. This is temporary while transitioning
2641 * all data to json.
2642 *
2643 * @since 4.02.03
2644 */
2645 public static function unserialize_or_decode( &$value ) {
2646 if ( is_array( $value ) ) {
2647 return;
2648 }
2649
2650 if ( is_serialized( $value ) ) {
2651 $value = maybe_unserialize( $value );
2652 } else {
2653 $value = self::maybe_json_decode( $value, false );
2654 }
2655 }
2656
2657 /**
2658 * Decode a JSON string.
2659 * Do not switch shortcodes like [24] to array unless intentional ie XML values.
2660 *
2661 * @param mixed $string
2662 * @param bool $single_to_array
2663 * @return mixed
2664 */
2665 public static function maybe_json_decode( $string, $single_to_array = true ) {
2666 if ( is_array( $string ) || is_null( $string ) ) {
2667 return $string;
2668 }
2669
2670 $new_string = json_decode( $string, true );
2671 if ( function_exists( 'json_last_error' ) ) {
2672 // php 5.3+
2673 $single_value = false;
2674 if ( ! $single_to_array ) {
2675 $single_value = is_array( $new_string ) && count( $new_string ) === 1 && isset( $new_string[0] );
2676 }
2677 if ( json_last_error() == JSON_ERROR_NONE && is_array( $new_string ) && ! $single_value ) {
2678 $string = $new_string;
2679 }
2680 }
2681
2682 return $string;
2683 }
2684
2685 /**
2686 * Reformat the json serialized array in name => value array.
2687 *
2688 * @since 4.02.03
2689 */
2690 public static function format_form_data( &$form ) {
2691 $formatted = array();
2692
2693 foreach ( $form as $input ) {
2694 if ( ! isset( $input['name'] ) ) {
2695 continue;
2696 }
2697 $key = $input['name'];
2698 if ( isset( $formatted[ $key ] ) ) {
2699 if ( is_array( $formatted[ $key ] ) ) {
2700 $formatted[ $key ][] = $input['value'];
2701 } else {
2702 $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
2703 }
2704 } else {
2705 $formatted[ $key ] = $input['value'];
2706 }
2707 }
2708
2709 parse_str( http_build_query( $formatted ), $form );
2710 }
2711
2712 /**
2713 * @since 4.02.03
2714 */
2715 public static function maybe_json_encode( $value ) {
2716 if ( is_array( $value ) ) {
2717 $value = wp_json_encode( $value );
2718 }
2719 return $value;
2720 }
2721
2722 /**
2723 * @since 1.07.10
2724 *
2725 * @param string $post_type The name of the post type that may need to be highlighted
2726 * echo The javascript to open and highlight the Formidable menu
2727 */
2728 public static function maybe_highlight_menu( $post_type ) {
2729 global $post;
2730
2731 if ( isset( $_REQUEST['post_type'] ) && $_REQUEST['post_type'] != $post_type ) {
2732 return;
2733 }
2734
2735 if ( is_object( $post ) && $post->post_type != $post_type ) {
2736 return;
2737 }
2738
2739 self::load_admin_wide_js();
2740 echo '<script type="text/javascript">jQuery(document).ready(function(){frmSelectSubnav();});</script>';
2741 }
2742
2743 /**
2744 * Load the JS file on non-Formidable pages in the admin area
2745 *
2746 * @since 2.0
2747 */
2748 public static function load_admin_wide_js( $load = true ) {
2749 $version = self::plugin_version();
2750 wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), $version );
2751
2752 $global_strings = array(
2753 'updating_msg' => __( 'Please wait while your site updates.', 'formidable' ),
2754 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
2755 'url' => self::plugin_url(),
2756 'app_url' => 'https://formidableforms.com/',
2757 'applicationsUrl' => admin_url( 'admin.php?page=formidable-applications' ),
2758 'canAccessApplicationDashboard' => current_user_can( is_callable( 'FrmProApplicationsHelper::get_required_templates_capability' ) ? FrmProApplicationsHelper::get_required_templates_capability() : 'frm_edit_forms' ),
2759 'loading' => __( 'Loading&hellip;', 'formidable' ),
2760 'nonce' => wp_create_nonce( 'frm_ajax' ),
2761 'proIncludesSliderJs' => is_callable( 'FrmProFormsHelper::prepare_custom_currency' ),
2762 );
2763 wp_localize_script( 'formidable_admin_global', 'frmGlobal', $global_strings );
2764
2765 if ( $load ) {
2766 wp_enqueue_script( 'formidable_admin_global' );
2767 }
2768 }
2769
2770 /**
2771 * @since 2.0.9
2772 */
2773 public static function load_font_style() {
2774 wp_enqueue_style( 'frm_fonts', self::plugin_url() . '/css/frm_fonts.css', array(), self::plugin_version() );
2775 }
2776
2777 /**
2778 * @param string $location
2779 */
2780 public static function localize_script( $location ) {
2781 global $wp_scripts;
2782
2783 $ajax_url = admin_url( 'admin-ajax.php', is_ssl() ? 'admin' : 'http' );
2784 $ajax_url = apply_filters( 'frm_ajax_url', $ajax_url );
2785
2786 $script_strings = array(
2787 'ajax_url' => $ajax_url,
2788 'images_url' => self::plugin_url() . '/images',
2789 'loading' => __( 'Loading&hellip;', 'formidable' ),
2790 'remove' => __( 'Remove', 'formidable' ),
2791 'offset' => apply_filters( 'frm_scroll_offset', 4 ),
2792 'nonce' => wp_create_nonce( 'frm_ajax' ),
2793 'id' => __( 'ID', 'formidable' ),
2794 'no_results' => __( 'No results match', 'formidable' ),
2795 'file_spam' => __( 'That file looks like Spam.', 'formidable' ),
2796 'calc_error' => __( 'There is an error in the calculation in the field with key', 'formidable' ),
2797 'empty_fields' => __( 'Please complete the preceding required fields before uploading a file.', 'formidable' ),
2798 'focus_first_error' => self::should_focus_first_error(),
2799 'include_alert_role' => self::should_include_alert_role_on_field_errors(),
2800 );
2801
2802 $data = $wp_scripts->get_data( 'formidable', 'data' );
2803 if ( empty( $data ) ) {
2804 wp_localize_script( 'formidable', 'frm_js', $script_strings );
2805 }
2806
2807 if ( $location == 'admin' ) {
2808 $frm_settings = self::get_settings();
2809 $admin_script_strings = array(
2810 'desc' => __( '(Click to add description)', 'formidable' ),
2811 'blank' => __( '(Blank)', 'formidable' ),
2812 'no_label' => __( '(no label)', 'formidable' ),
2813 'saving' => esc_attr( __( 'Saving', 'formidable' ) ),
2814 'saved' => esc_attr( __( 'Saved', 'formidable' ) ),
2815 'ok' => __( 'OK', 'formidable' ),
2816 'cancel' => __( 'Cancel', 'formidable' ),
2817 'default_label' => __( 'Default', 'formidable' ),
2818 'clear_default' => __( 'Clear default value when typing', 'formidable' ),
2819 'no_clear_default' => __( 'Do not clear default value when typing', 'formidable' ),
2820 'valid_default' => __( 'Default value will pass form validation', 'formidable' ),
2821 'no_valid_default' => __( 'Default value will NOT pass form validation', 'formidable' ),
2822 'caution' => __( 'Heads up', 'formidable' ),
2823 'confirm' => __( 'Are you sure?', 'formidable' ),
2824 'conf_delete' => __( 'Are you sure you want to delete this field and all data associated with it?', 'formidable' ),
2825 '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' ),
2826 'conf_no_repeat' => __( 'Warning: If you have entries with multiple rows, all but the first row will be lost.', 'formidable' ),
2827 'default_unique' => $frm_settings->unique_msg,
2828 'default_conf' => __( 'The entered values do not match', 'formidable' ),
2829 'enter_email' => __( 'Enter Email', 'formidable' ),
2830 'confirm_email' => __( 'Confirm Email', 'formidable' ),
2831 'conditional_text' => __( 'Conditional content here', 'formidable' ),
2832 'new_option' => __( 'New Option', 'formidable' ),
2833 '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' ),
2834 'enter_password' => __( 'Enter Password', 'formidable' ),
2835 'confirm_password' => __( 'Confirm Password', 'formidable' ),
2836 'import_complete' => __( 'Import Complete', 'formidable' ),
2837 'updating' => __( 'Please wait while your site updates.', 'formidable' ),
2838 'no_save_warning' => __( 'Warning: There is no way to retrieve unsaved entries.', 'formidable' ),
2839 'private_label' => __( 'Private', 'formidable' ),
2840 'jquery_ui_url' => '',
2841 'pro_url' => is_callable( 'FrmProAppHelper::plugin_url' ) ? FrmProAppHelper::plugin_url() : '',
2842 'no_licenses' => __( 'No new licenses were found', 'formidable' ),
2843 'unmatched_parens' => __( 'This calculation has at least one unmatched ( ) { } [ ].', 'formidable' ),
2844 'view_shortcodes' => __( 'This calculation may have shortcodes that work in Views but not forms.', 'formidable' ),
2845 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
2846 'only_one_action' => __( 'This form action is limited to one per form. Please edit the existing form action.', 'formidable' ),
2847 'unsafe_params' => FrmFormsHelper::reserved_words(),
2848 /* Translators: %s is the name of a Detail Page Slug that is a reserved word.*/
2849 'slug_is_reserved' => sprintf( __( 'The Detail Page Slug "%s" is reserved by WordPress. This may cause problems. Is this intentional?', 'formidable' ), '****' ),
2850 /* 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. */
2851 'param_is_reserved' => sprintf( __( 'The parameter "%s" is reserved by WordPress. This may cause problems when included in the URL. Is this intentional? ', 'formidable' ), '****' ),
2852 'reserved_words' => __( 'See the list of reserved words in WordPress.', 'formidable' ),
2853 'repeat_limit_min' => __( 'Please enter a Repeat Limit that is greater than 1.', 'formidable' ),
2854 'checkbox_limit' => __( 'Please select a limit between 0 and 200.', 'formidable' ),
2855 'install' => __( 'Install', 'formidable' ),
2856 'active' => __( 'Active', 'formidable' ),
2857 'select_a_field' => __( 'Select a Field', 'formidable' ),
2858 'no_items_found' => __( 'No items found.', 'formidable' ),
2859 );
2860 $admin_script_strings = apply_filters( 'frm_admin_script_strings', $admin_script_strings );
2861
2862 $data = $wp_scripts->get_data( 'formidable_admin', 'data' );
2863 if ( empty( $data ) ) {
2864 wp_localize_script( 'formidable_admin', 'frm_admin_js', $admin_script_strings );
2865 }
2866 }
2867 }
2868
2869 /**
2870 * Returns whether or not the first errored input should be auto-focused (default true).
2871 *
2872 * @since 5.2.05
2873 *
2874 * @return bool
2875 */
2876 private static function should_focus_first_error() {
2877 return (bool) apply_filters( 'frm_focus_first_error', true );
2878 }
2879
2880 /**
2881 * Returns whether or not field errors should include role="alert" (default true).
2882 *
2883 * @since 5.2.05
2884 *
2885 * @return bool
2886 */
2887 public static function should_include_alert_role_on_field_errors() {
2888 return (bool) apply_filters( 'frm_include_alert_role_on_field_errors', true );
2889 }
2890
2891 /**
2892 * Echo the message on the plugins listing page
2893 *
2894 * @since 1.07.10
2895 *
2896 * @param float $min_version The version the add-on requires
2897 */
2898 public static function min_version_notice( $min_version ) {
2899 $frm_version = self::plugin_version();
2900
2901 // Check if Formidable meets minimum requirements.
2902 if ( version_compare( $frm_version, $min_version, '>=' ) ) {
2903 return;
2904 }
2905
2906 $wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
2907 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">' .
2908 esc_html__( 'You are running an outdated version of Formidable. This plugin may not work correctly if you do not update Formidable.', 'formidable' ) .
2909 '</div></td></tr>';
2910 }
2911
2912 /**
2913 * If Pro is far outdated, show a message.
2914 *
2915 * @since 4.0.01
2916 *
2917 * @return void
2918 */
2919 public static function min_pro_version_notice( $min_version ) {
2920 if ( ! self::is_formidable_admin() ) {
2921 // Don't show admin-wide.
2922 return;
2923 }
2924
2925 self::php_version_notice();
2926
2927 $is_pro = self::pro_is_installed() && class_exists( 'FrmProDb' );
2928 if ( ! $is_pro || self::meets_min_pro_version( $min_version ) ) {
2929 return;
2930 }
2931
2932 $pro_version = FrmProDb::$plug_version;
2933 $expired = FrmAddonsController::is_license_expired();
2934 ?>
2935 <div class="error frm_previous_install">
2936 <?php
2937 esc_html_e( 'You are running a version of Formidable Forms that may not be compatible with your version of Formidable Forms Pro.', 'formidable' );
2938 if ( empty( $expired ) ) {
2939 echo ' Please <a href="' . esc_url( admin_url( 'plugins.php?s=formidable%20forms%20pro' ) ) . '">update now</a>.';
2940 } else {
2941 echo '<br/>Please <a href="https://formidableforms.com/account/downloads/?utm_source=WordPress&utm_medium=outdated">renew now</a> to get the latest version.';
2942 }
2943 ?>
2944 </div>
2945 <?php
2946 }
2947
2948 /**
2949 * If Pro is installed, check the version number.
2950 *
2951 * @since 4.0.01
2952 */
2953 public static function meets_min_pro_version( $min_version ) {
2954 return ! class_exists( 'FrmProDb' ) || version_compare( FrmProDb::$plug_version, $min_version, '>=' );
2955 }
2956
2957 /**
2958 * Show a message if the browser or PHP version is below the recommendations.
2959 *
2960 * @since 4.0.02
2961 */
2962 private static function php_version_notice() {
2963 $message = array();
2964 if ( version_compare( phpversion(), '5.6', '<' ) ) {
2965 $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' );
2966 }
2967
2968 $browser = self::get_server_value( 'HTTP_USER_AGENT' );
2969 $is_ie = strpos( $browser, 'MSIE' ) !== false;
2970 if ( $is_ie ) {
2971 $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' );
2972 }
2973
2974 foreach ( $message as $m ) {
2975 ?>
2976 <div class="error frm_previous_install">
2977 <?php echo esc_html( $m ); ?>
2978 </div>
2979 <?php
2980 }
2981 }
2982
2983 public static function locales( $type = 'date' ) {
2984 $locales = array(
2985 'en' => __( 'English', 'formidable' ),
2986 'af' => __( 'Afrikaans', 'formidable' ),
2987 'sq' => __( 'Albanian', 'formidable' ),
2988 'ar' => __( 'Arabic', 'formidable' ),
2989 'hy' => __( 'Armenian', 'formidable' ),
2990 'az' => __( 'Azerbaijani', 'formidable' ),
2991 'eu' => __( 'Basque', 'formidable' ),
2992 'bs' => __( 'Bosnian', 'formidable' ),
2993 'bg' => __( 'Bulgarian', 'formidable' ),
2994 'ca' => __( 'Catalan', 'formidable' ),
2995 'zh-HK' => __( 'Chinese Hong Kong', 'formidable' ),
2996 'zh-CN' => __( 'Chinese Simplified', 'formidable' ),
2997 'zh-TW' => __( 'Chinese Traditional', 'formidable' ),
2998 'hr' => __( 'Croatian', 'formidable' ),
2999 'cs' => __( 'Czech', 'formidable' ),
3000 'da' => __( 'Danish', 'formidable' ),
3001 'nl' => __( 'Dutch', 'formidable' ),
3002 'en-GB' => __( 'English/UK', 'formidable' ),
3003 'eo' => __( 'Esperanto', 'formidable' ),
3004 'et' => __( 'Estonian', 'formidable' ),
3005 'fo' => __( 'Faroese', 'formidable' ),
3006 'fa' => __( 'Farsi/Persian', 'formidable' ),
3007 'fil' => __( 'Filipino', 'formidable' ),
3008 'fi' => __( 'Finnish', 'formidable' ),
3009 'fr' => __( 'French', 'formidable' ),
3010 'fr-CA' => __( 'French/Canadian', 'formidable' ),
3011 'fr-CH' => __( 'French/Swiss', 'formidable' ),
3012 'de' => __( 'German', 'formidable' ),
3013 'de-AT' => __( 'German/Austria', 'formidable' ),
3014 'de-CH' => __( 'German/Switzerland', 'formidable' ),
3015 'el' => __( 'Greek', 'formidable' ),
3016 'he' => __( 'Hebrew', 'formidable' ),
3017 'iw' => __( 'Hebrew', 'formidable' ),
3018 'hi' => __( 'Hindi', 'formidable' ),
3019 'hu' => __( 'Hungarian', 'formidable' ),
3020 'is' => __( 'Icelandic', 'formidable' ),
3021 'id' => __( 'Indonesian', 'formidable' ),
3022 'it' => __( 'Italian', 'formidable' ),
3023 'ja' => __( 'Japanese', 'formidable' ),
3024 'ko' => __( 'Korean', 'formidable' ),
3025 'lv' => __( 'Latvian', 'formidable' ),
3026 'lt' => __( 'Lithuanian', 'formidable' ),
3027 'ms' => __( 'Malaysian', 'formidable' ),
3028 'no' => __( 'Norwegian', 'formidable' ),
3029 'pl' => __( 'Polish', 'formidable' ),
3030 'pt' => __( 'Portuguese', 'formidable' ),
3031 'pt-BR' => __( 'Portuguese/Brazilian', 'formidable' ),
3032 'pt-PT' => __( 'Portuguese/Portugal', 'formidable' ),
3033 'ro' => __( 'Romanian', 'formidable' ),
3034 'ru' => __( 'Russian', 'formidable' ),
3035 'sr' => __( 'Serbian', 'formidable' ),
3036 'sr-SR' => __( 'Serbian', 'formidable' ),
3037 'sk' => __( 'Slovak', 'formidable' ),
3038 'sl' => __( 'Slovenian', 'formidable' ),
3039 'es' => __( 'Spanish', 'formidable' ),
3040 'es-419' => __( 'Spanish/Latin America', 'formidable' ),
3041 'sv' => __( 'Swedish', 'formidable' ),
3042 'ta' => __( 'Tamil', 'formidable' ),
3043 'th' => __( 'Thai', 'formidable' ),
3044 'tr' => __( 'Turkish', 'formidable' ),
3045 'uk' => __( 'Ukrainian', 'formidable' ),
3046 'vi' => __( 'Vietnamese', 'formidable' ),
3047 );
3048
3049 if ( $type === 'captcha' ) {
3050 // remove the languages unavailable for the captcha
3051 $unset = array( 'af', 'sq', 'hy', 'az', 'eu', 'bs', 'zh-HK', 'eo', 'et', 'fo', 'fr-CH', 'he', 'is', 'ms', 'sr-SR', 'ta' );
3052 } else {
3053 // remove the languages unavailable for the datepicker
3054 $unset = array( 'fil', 'fr-CA', 'de-AT', 'de-CH', 'iw', 'hi', 'pt', 'pt-PT', 'es-419' );
3055 }
3056
3057 $locales = array_diff_key( $locales, array_flip( $unset ) );
3058 $locales = apply_filters( 'frm_locales', $locales );
3059
3060 return $locales;
3061 }
3062
3063 /**
3064 * Output HTML containing reference text for accessibility
3065 *
3066 * @deprecated 5.1
3067 */
3068 public static function multiselect_accessibility() {
3069 _deprecated_function( __METHOD__, '5.1' );
3070 }
3071
3072 public static function get_menu_icon_class() {
3073 if ( is_callable( 'FrmProAppHelper::get_settings' ) ) {
3074 $settings = FrmProAppHelper::get_settings();
3075 if ( is_object( $settings ) && ! empty( $settings->menu_icon ) ) {
3076 return $settings->menu_icon;
3077 }
3078 }
3079 return 'frmfont frm_logo_icon';
3080 }
3081
3082 /**
3083 * Shows the images dropdown.
3084 *
3085 * @since 5.0.04
3086 *
3087 * @param array $args {
3088 * Arguments.
3089 *
3090 * @type string $selected Selected value.
3091 * @type array[] $options Array of options with keys are option values and values are array.
3092 * The option array contains `text`, `svg` and `custom_atts`.
3093 * @type string $classes Custom CSS classes for the wrapper element.
3094 * @type array $input_attrs Attributes of value input.
3095 * }
3096 */
3097 public static function images_dropdown( $args ) {
3098 $args = self::fill_default_images_dropdown_args( $args );
3099
3100 $input_attrs_str = self::get_images_dropdown_input_attrs( $args );
3101 ob_start();
3102 include self::plugin_path() . '/classes/views/shared/images-dropdown.php';
3103 $output = ob_get_clean();
3104
3105 /**
3106 * Allows modifying the output of FrmAppHelper::images_dropdown() method.
3107 *
3108 * @since 5.0.04
3109 *
3110 * @param string $output The output.
3111 * @param array $args Passed arguments.
3112 */
3113 echo apply_filters( 'frm_images_dropdown_output', $output, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
3114 }
3115
3116 /**
3117 * Fills the default images_dropdown() arguments.
3118 *
3119 * @since 5.0.04
3120 *
3121 * @param array $args The arguments.
3122 * @return array
3123 */
3124 private static function fill_default_images_dropdown_args( $args ) {
3125 $defaults = array(
3126 'selected' => '',
3127 'options' => array(),
3128 'classes' => '',
3129 'input_attrs' => array(),
3130 );
3131 $new_args = wp_parse_args( $args, $defaults );
3132
3133 $new_args['options'] = (array) $new_args['options'];
3134 $new_args['input_attrs'] = (array) $new_args['input_attrs'];
3135
3136 // Set the number of columns.
3137 $new_args['col_class'] = ceil( 12 / count( $new_args['options'] ) );
3138 if ( $new_args['col_class'] > 6 ) {
3139 $new_args['col_class'] = ceil( $new_args['col_class'] / 2 );
3140 }
3141
3142 /**
3143 * Allows modifying the arguments of images_dropdown() method.
3144 *
3145 * @since 5.0.04
3146 *
3147 * @param array $new_args Arguments after filling the defaults.
3148 * @param array $args Arguments passed to the method, before filling the defaults.
3149 */
3150 return apply_filters( 'frm_images_dropdown_args', $new_args, $args );
3151 }
3152
3153 /**
3154 * Gets HTML attributes of the input in images_dropdown() method.
3155 *
3156 * @since 5.0.04
3157 *
3158 * @param array $args The arguments.
3159 * @return string
3160 */
3161 private static function get_images_dropdown_input_attrs( $args ) {
3162 $input_attrs = $args['input_attrs'];
3163 $input_attrs['type'] = 'radio';
3164 $input_attrs['name'] = $args['name'];
3165
3166 $input_attrs_str = '';
3167 foreach ( $input_attrs as $key => $input_attr ) {
3168 $input_attrs_str .= ' ' . sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $input_attr ) );
3169 }
3170
3171 /**
3172 * Allows modifying the HTML attributes of the input in images_dropdown() method.
3173 *
3174 * @since 5.0.04
3175 *
3176 * @param string $input_attrs_str HTML attributes string.
3177 * @param array $args The arguments of images_dropdown() method.
3178 */
3179 return apply_filters( 'frm_images_dropdown_input_attrs', $input_attrs_str, $args );
3180 }
3181
3182 /**
3183 * Gets the image of each option in images_dropdown() method.
3184 *
3185 * @since 5.0.04
3186 *
3187 * @param array $option Option data.
3188 * @param array $args The arguments of images_dropdown() method.
3189 * @return string
3190 */
3191 private static function get_images_dropdown_option_image( $option, $args ) {
3192 $image = self::icon_by_class(
3193 'frmfont ' . $option['svg'],
3194 array(
3195 'echo' => false,
3196 )
3197 );
3198
3199 $args['option'] = $option;
3200
3201 /**
3202 * Allows modifying the image of each option in images_dropdown() method.
3203 *
3204 * @since 5.0.04
3205 *
3206 * @param string $image The image HTML.
3207 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3208 */
3209 return apply_filters( 'frm_images_dropdown_option_image', $image, $args );
3210 }
3211
3212 /**
3213 * Gets the HTML classes of each option in images_dropdown() method.
3214 *
3215 * @since 5.0.04
3216 *
3217 * @param array $option Option data.
3218 * @param array $args The arguments of images_dropdown() method.
3219 * @return string
3220 */
3221 private static function get_images_dropdown_option_classes( $option, $args ) {
3222 $classes = '';
3223
3224 if ( ! empty( $option['custom_attrs']['class'] ) ) {
3225 $classes .= ' ' . $option['custom_attrs']['class'];
3226 }
3227
3228 $args['option'] = $option;
3229
3230 /**
3231 * Allows modifying the CSS classes of each option in images_dropdown() method.
3232 *
3233 * @since 5.0.04
3234 *
3235 * @param string $classes CSS classes.
3236 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3237 */
3238 return apply_filters( 'frm_images_dropdown_option_classes', $classes, $args );
3239 }
3240
3241 /**
3242 * Gets the custom HTML attributes 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_html_attrs( $option, $args ) {
3251 $html_attrs = '';
3252 if ( ! empty( $option['custom_attrs'] ) && is_array( $option['custom_attrs'] ) ) {
3253 $html_attrs_arr = array();
3254
3255 foreach ( $option['custom_attrs'] as $key => $value ) {
3256 if ( in_array( $key, array( 'type', 'class', 'data-value' ) ) ) {
3257 continue;
3258 }
3259
3260 $html_attrs_arr[] = sprintf( '%s="%s"', esc_attr( $key ), esc_attr( $value ) );
3261 }
3262
3263 $html_attrs = implode( ' ', $html_attrs_arr );
3264 }
3265
3266 $args['option'] = $option;
3267
3268 /**
3269 * Allows modifying the custom HTML attributes of each option in images_dropdown() method.
3270 *
3271 * @since 5.0.04
3272 *
3273 * @param string $html_attrs The HTML attributes string.
3274 * @param array $args The arguments of images_dropdown() method, with `option` array is added.
3275 */
3276 return apply_filters( 'frm_images_dropdown_option_html_attrs', $html_attrs, $args );
3277 }
3278
3279 /**
3280 * @since 5.0.07
3281 *
3282 * @return bool true if the current user is allowed to save unfiltered HTML.
3283 */
3284 public static function allow_unfiltered_html() {
3285 if ( self::should_never_allow_unfiltered_html() ) {
3286 return false;
3287 }
3288 return current_user_can( 'unfiltered_html' );
3289 }
3290
3291 /**
3292 * @since 5.0.13
3293 *
3294 * @return bool
3295 */
3296 public static function should_never_allow_unfiltered_html() {
3297 if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) {
3298 return true;
3299 }
3300
3301 /**
3302 * Formidable will check DISALLOW_UNFILTERED_HTML to determine if some form HTML should be filtered or not.
3303 * In many cases, scripts are added intentionally to forms and will not be stripped if DISALLOW_UNFILTERED_HTML is not set.
3304 * It is also possible to filter Formidable without defining DISALLOW_UNFILTERED_HTML, with add_filter( 'frm_disallow_unfiltered_html', '__return_true' );
3305 *
3306 * @since 5.0.13
3307 */
3308 return apply_filters( 'frm_disallow_unfiltered_html', false );
3309 }
3310
3311 /**
3312 * @since 5.0.07
3313 *
3314 * @param array $values
3315 * @param array $keys
3316 * @return array
3317 */
3318 public static function maybe_filter_array( $values, $keys ) {
3319 $allow_unfiltered_html = self::allow_unfiltered_html();
3320
3321 if ( $allow_unfiltered_html ) {
3322 return $values;
3323 }
3324
3325 foreach ( $keys as $key ) {
3326 if ( isset( $values[ $key ] ) ) {
3327 $values[ $key ] = self::kses( $values[ $key ], 'all' );
3328 }
3329 }
3330
3331 return $values;
3332 }
3333
3334 /**
3335 * Some back end fields allow privleged users to add scripts.
3336 * A site that uses the DISALLOW_UNFILTERED_HTML always remove scripts on echo.
3337 *
3338 * @since 5.0.13
3339 *
3340 * @param string $value
3341 * @param array|string $allowed 'all' for everything included as defaults
3342 * @return string
3343 */
3344 public static function maybe_kses( $value, $allowed = 'all' ) {
3345 if ( self::should_never_allow_unfiltered_html() ) {
3346 $value = self::kses( $value, $allowed );
3347 }
3348 return $value;
3349 }
3350
3351 /**
3352 * @since 5.0.16
3353 *
3354 * @return bool
3355 */
3356 public static function show_landing_pages() {
3357 return self::show_new_feature( 'landing' );
3358 }
3359
3360 /**
3361 * @since 5.0.16
3362 *
3363 * @return array
3364 */
3365 public static function get_landing_page_upgrade_data_params( $medium = 'landing' ) {
3366 $params = array(
3367 'medium' => $medium,
3368 'upgrade' => __( 'Form Landing Pages', 'formidable' ),
3369 'message' => __( 'Easily manage a landing page for your form. Upgrade to get form landing pages.', 'formidable' ),
3370 'screenshot' => 'landing.png',
3371 );
3372 return self::get_upgrade_data_params( 'landing', $params );
3373 }
3374
3375 /**
3376 * @since 5.0.17
3377 *
3378 * @param string $plugin
3379 * @return string|false
3380 */
3381 private static function get_plan_required( $plugin ) {
3382 $link = FrmAddonsController::install_link( $plugin );
3383 return FrmFormsHelper::get_plan_required( $link );
3384 }
3385
3386 /**
3387 * @since 5.0.17
3388 *
3389 * @param string
3390 * @return bool
3391 */
3392 public static function show_new_feature( $feature ) {
3393 $link = FrmAddonsController::install_link( $feature );
3394 return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );
3395 }
3396
3397 /**
3398 * @since 5.0.17
3399 *
3400 * @param string $plugin
3401 * @param array $params
3402 * @return array
3403 */
3404 public static function get_upgrade_data_params( $plugin, $params ) {
3405 $link = FrmAddonsController::install_link( $plugin );
3406 if ( ! $link ) {
3407 return $params;
3408 }
3409
3410 if ( ! empty( $link['url'] ) && self::pro_is_installed() ) {
3411 $params['oneclick'] = json_encode( $link );
3412 unset( $params['message'] );
3413 if ( ! isset( $params['medium'] ) ) {
3414 $params['medium'] = $plugin;
3415 }
3416 } else {
3417 $params['requires'] = FrmFormsHelper::get_plan_required( $link );
3418 }
3419
3420 return $params;
3421 }
3422
3423 /**
3424 * 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.
3425 * Not every server installs the ctype extension, so use a fallback if the function does not exist.
3426 *
3427 * @since 5.0.17
3428 *
3429 * @param string $text
3430 * @return bool
3431 */
3432 public static function ctype_xdigit( $text ) {
3433 if ( function_exists( 'ctype_xdigit' ) ) {
3434 return ctype_xdigit( $text );
3435 }
3436 return is_string( $text ) && '' !== $text && ! preg_match( '/[^A-Fa-f0-9]/', $text );
3437 }
3438
3439 /**
3440 * Set the current screen to avoid undefined notices.
3441 *
3442 * @since 5.2.01
3443 */
3444 public static function set_current_screen_and_hook_suffix() {
3445 global $hook_suffix;
3446 if ( is_null( $hook_suffix ) ) {
3447 // $hook_suffix gets used in substr so make sure it's not null. PHP 8.1 deprecates null in substr.
3448 $hook_suffix = ''; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
3449 }
3450 set_current_screen();
3451 }
3452
3453 /**
3454 * Shows pill text.
3455 *
3456 * @since 5.2.02
3457 *
3458 * @param string $text Text in the pill. Default is NEW.
3459 */
3460 public static function show_pill_text( $text = null ) {
3461 if ( null === $text ) {
3462 $text = __( 'NEW', 'formidable' );
3463 }
3464 echo '<span class="frm-new-pill">' . esc_html( $text ) . '</span>';
3465 }
3466
3467 /**
3468 * Count the number of decimals digits.
3469 *
3470 * @since 5.2.07
3471 *
3472 * @param mixed $num Number.
3473 * @return int|false Returns `false` if the passed parameter is not number.
3474 */
3475 public static function count_decimals( $num ) {
3476 if ( ! is_numeric( $num ) ) {
3477 return false;
3478 }
3479
3480 $num = (string) $num;
3481 $parts = explode( '.', $num );
3482 if ( 1 === count( $parts ) ) {
3483 return 0;
3484 }
3485
3486 return strlen( $parts[ count( $parts ) - 1 ] );
3487 }
3488
3489 /**
3490 * Prevent a fatal error in PHP8 if gmt_offset happens to be set an empty string.
3491 * This is a bug in WordPress. It isn't safe to call current_time( 'timestamp' ) without this with an empty string offset.
3492 * In the future this might be safe to remove. Keep an eye on the current_time function in functions.php.
3493 *
3494 * @since 5.3.1
3495 *
3496 * @return void
3497 */
3498 public static function filter_gmt_offset() {
3499 if ( self::$added_gmt_offset_filter ) {
3500 // Avoid adding twice.
3501 return;
3502 }
3503
3504 add_filter(
3505 'option_gmt_offset',
3506 function( $offset ) {
3507 if ( ! is_string( $offset ) || is_numeric( $offset ) ) {
3508 // Leave a valid value alone.
3509 return $offset;
3510 }
3511
3512 return 0;
3513 }
3514 );
3515 self::$added_gmt_offset_filter = true;
3516 }
3517
3518 /**
3519 * @since 5.3.1
3520 *
3521 * @return bool
3522 */
3523 public static function on_form_listing_page() {
3524 if ( ! self::is_admin_page( 'formidable' ) ) {
3525 return false;
3526 }
3527
3528 $action = self::simple_get( 'frm_action', 'sanitize_title' );
3529 return ! $action || in_array( $action, self::get_form_listing_page_actions(), true );
3530 }
3531
3532 /**
3533 * Get all actions that also display the forms list.
3534 *
3535 * @since 5.3.1
3536 *
3537 * @return array<string>
3538 */
3539 private static function get_form_listing_page_actions() {
3540 return array( 'list', 'trash', 'untrash', 'destroy' );
3541 }
3542
3543 /**
3544 * @since 4.08
3545 * @deprecated 4.09.01
3546 */
3547 public static function expiring_message() {
3548 _deprecated_function( __METHOD__, '4.09.01', 'FrmProAddonsController::expiring_message' );
3549 if ( is_callable( 'FrmProAddonsController::expiring_message' ) ) {
3550 FrmProAddonsController::expiring_message();
3551 }
3552 }
3553
3554 /**
3555 * Use the WP 4.7 wp_doing_ajax function
3556 *
3557 * @since 2.05.07
3558 * @deprecated 4.04.04
3559 */
3560 public static function wp_doing_ajax() {
3561 _deprecated_function( __METHOD__, '4.04.04', 'wp_doing_ajax' );
3562 return wp_doing_ajax();
3563 }
3564
3565 /**
3566 * @deprecated 4.0
3567 */
3568 public static function insert_opt_html( $args ) {
3569 _deprecated_function( __METHOD__, '4.0', 'FrmFormsHelper::insert_opt_html' );
3570 FrmFormsHelper::insert_opt_html( $args );
3571 }
3572
3573 /**
3574 * Used to filter shortcode in text widgets
3575 *
3576 * @deprecated 2.5.4
3577 * @codeCoverageIgnore
3578 */
3579 public static function widget_text_filter_callback( $matches ) {
3580 return FrmDeprecated::widget_text_filter_callback( $matches );
3581 }
3582
3583 /**
3584 * @deprecated 3.01
3585 * @codeCoverageIgnore
3586 */
3587 public static function sanitize_array( &$values ) {
3588 FrmDeprecated::sanitize_array( $values );
3589 }
3590
3591 /**
3592 * @param array $settings
3593 * @param string $group
3594 *
3595 * @since 2.0.6
3596 * @deprecated 2.05.06
3597 * @codeCoverageIgnore
3598 */
3599 public static function save_settings( $settings, $group ) {
3600 return FrmDeprecated::save_settings( $settings, $group );
3601 }
3602
3603 /**
3604 * @since 2.0.4
3605 * @deprecated 2.05.06
3606 * @codeCoverageIgnore
3607 */
3608 public static function save_json_post( $settings ) {
3609 return FrmDeprecated::save_json_post( $settings );
3610 }
3611
3612 /**
3613 * @since 2.0
3614 * @deprecated 2.05.06
3615 * @codeCoverageIgnore
3616 *
3617 * @param string $cache_key The unique name for this cache
3618 * @param string $group The name of the cache group
3619 * @param string $query If blank, don't run a db call
3620 * @param string $type The wpdb function to use with this query
3621 *
3622 * @return mixed $results The cache or query results
3623 */
3624 public static function check_cache( $cache_key, $group = '', $query = '', $type = 'get_var', $time = 300 ) {
3625 return FrmDeprecated::check_cache( $cache_key, $group, $query, $type, $time );
3626 }
3627
3628 /**
3629 * @deprecated 2.05.06
3630 * @codeCoverageIgnore
3631 */
3632 public static function set_cache( $cache_key, $results, $group = '', $time = 300 ) {
3633 return FrmDeprecated::set_cache( $cache_key, $results, $group, $time );
3634 }
3635
3636 /**
3637 * @deprecated 2.05.06
3638 * @codeCoverageIgnore
3639 */
3640 public static function add_key_to_group_cache( $key, $group ) {
3641 FrmDeprecated::add_key_to_group_cache( $key, $group );
3642 }
3643
3644 /**
3645 * @deprecated 2.05.06
3646 * @codeCoverageIgnore
3647 */
3648 public static function get_group_cached_keys( $group ) {
3649 return FrmDeprecated::get_group_cached_keys( $group );
3650 }
3651
3652 /**
3653 * @since 2.0
3654 * @deprecated 2.05.06
3655 * @codeCoverageIgnore
3656 * @return mixed The cached value or false
3657 */
3658 public static function check_cache_and_transient( $cache_key ) {
3659 return FrmDeprecated::check_cache( $cache_key );
3660 }
3661
3662 /**
3663 * @since 2.0
3664 * @deprecated 2.05.06
3665 * @codeCoverageIgnore
3666 *
3667 * @param string $cache_key
3668 */
3669 public static function delete_cache_and_transient( $cache_key, $group = 'default' ) {
3670 FrmDeprecated::delete_cache_and_transient( $cache_key, $group );
3671 }
3672
3673 /**
3674 * @since 2.0
3675 * @deprecated 2.05.06
3676 * @codeCoverageIgnore
3677 *
3678 * @param string $group The name of the cache group
3679 */
3680 public static function cache_delete_group( $group ) {
3681 FrmDeprecated::cache_delete_group( $group );
3682 }
3683
3684 /**
3685 * @since 1.07.10
3686 * @deprecated 2.05.06
3687 * @codeCoverageIgnore
3688 *
3689 * @param string $term The value to escape
3690 *
3691 * @return string The escaped value
3692 */
3693 public static function esc_like( $term ) {
3694 return FrmDeprecated::esc_like( $term );
3695 }
3696
3697 /**
3698 * @param string $order_query
3699 *
3700 * @deprecated 2.05.06
3701 * @codeCoverageIgnore
3702 */
3703 public static function esc_order( $order_query ) {
3704 return FrmDeprecated::esc_order( $order_query );
3705 }
3706
3707 /**
3708 * @deprecated 2.05.06
3709 * @codeCoverageIgnore
3710 */
3711 public static function esc_order_by( &$order_by ) {
3712 FrmDeprecated::esc_order_by( $order_by );
3713 }
3714
3715 /**
3716 * @param string $limit
3717 *
3718 * @deprecated 2.05.06
3719 * @codeCoverageIgnore
3720 */
3721 public static function esc_limit( $limit ) {
3722 return FrmDeprecated::esc_limit( $limit );
3723 }
3724
3725 /**
3726 * @since 2.0
3727 * @deprecated 2.05.06
3728 * @codeCoverageIgnore
3729 */
3730 public static function prepare_array_values( $array, $type = '%s' ) {
3731 return FrmDeprecated::prepare_array_values( $array, $type );
3732 }
3733
3734 /**
3735 * @deprecated 2.05.06
3736 * @codeCoverageIgnore
3737 */
3738 public static function prepend_and_or_where( $starts_with = ' WHERE ', $where = '' ) {
3739 return FrmDeprecated::prepend_and_or_where( $starts_with, $where );
3740 }
3741
3742 /**
3743 * @since 2.0
3744 * @deprecated 5.0.13
3745 *
3746 * @return string The base Google APIS url for the current version of jQuery UI
3747 */
3748 public static function jquery_ui_base_url() {
3749 _deprecated_function( __FUNCTION__, '5.0.13', 'FrmProAppHelper::jquery_ui_base_url' );
3750 return is_callable( 'FrmProAppHelper::jquery_ui_base_url' ) ? FrmProAppHelper::jquery_ui_base_url() : '';
3751 }
3752
3753 /**
3754 * @since 4.07
3755 * @deprecated x.x
3756 */
3757 public static function renewal_message() {
3758 _deprecated_function( __METHOD__, 'x.x', 'FrmProAddonsController::renewal_message' );
3759 }
3760 }
3761