PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
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
← All changes | classes/helpers/FrmAppHelper.php +180 -44 6.29 → trunk View file →
@@ -9,9 +9,9 @@
9 9 * Version of the database we are moving to.
10 10 *
11 11 * @var int
12 12 */
13 - public static $db_version = 104;
13 + public static $db_version = 106;
14 14
15 15 /**
16 16 * Used by the API add-on.
17 17 *
@@ -28,9 +28,9 @@
28 28 * @since 2.0
29 29 *
30 30 * @var string
31 31 */
32 - public static $plug_version = '6.29';
32 + public static $plug_version = '6.35';
33 33
34 34 /**
35 35 * @var bool
36 36 */
@@ -309,10 +309,9 @@
309 309 if ( ! self::pro_is_installed() ) {
310 310 return true;
311 311 }
312 312
313 - $menu_icon = self::get_menu_icon_class();
314 - return str_contains( $menu_icon, 'frm_logo_icon' );
313 + return str_contains( self::get_menu_icon_class(), 'frm_logo_icon' );
315 314 }
316 315
317 316 /**
318 317 * @since 3.05
@@ -938,8 +937,9 @@
938 937 } else {
939 938 self::sanitize_value( self::class . '::strip_most_html', $value );
940 939 }
941 940 self::decode_specialchars( $value );
941 + self::sanitize_value( 'FrmHtmlSanitizer::sanitize_url_attributes', $value );
942 942 }
943 943
944 944 /**
945 945 * Allow only a small set of very basic HTML for unprivileged users.
@@ -1050,10 +1050,9 @@
1050 1050 *
1051 1051 * @return string
1052 1052 */
1053 1053 public static function kses( $value, $allowed = array() ) {
1054 - $allowed_html = self::allowed_html( $allowed );
1055 - return wp_kses( $value, $allowed_html );
1054 + return wp_kses( $value, self::allowed_html( $allowed ) );
1056 1055 }
1057 1056
1058 1057 /**
1059 1058 * Sanitizes and echoes a given value.
@@ -1370,10 +1369,9 @@
1370 1369 if ( isset( $atts['echo'] ) ) {
1371 1370 unset( $atts['echo'] );
1372 1371 }
1373 1372
1374 - $html_atts = self::array_to_html_params( $atts );
1375 - $icon = trim( str_replace( array( 'frm_icon_font', 'frmfont ' ), '', $class ) );
1373 + $icon = trim( str_replace( array( 'frm_icon_font', 'frmfont ' ), '', $class ) );
1376 1374
1377 1375 // Replace icons that have been removed or renamed.
1378 1376 $deprecated = array(
1379 1377 'frm_clone_solid_icon' => 'frm_clone_icon',
@@ -1385,11 +1383,11 @@
1385 1383 $icon = $deprecated[ $icon ];
1386 1384 $class = str_replace( $icon, $deprecated[ $icon ], $class );
1387 1385 }
1388 1386
1389 - if ( $icon === $class ) {
1390 - $icon = '<i class="' . esc_attr( $class ) . '"' . $html_atts . '></i>';
1391 - } else {
1387 + $is_font_icon = $icon === $class;
1388 +
1389 + if ( ! $is_font_icon ) {
1392 1390 $class = str_contains( $icon, ' ' ) ? ' ' . $icon : '';
1393 1391
1394 1392 if ( str_contains( $icon, ' ' ) ) {
1395 1393 $icon = explode( ' ', $icon );
@@ -1394,18 +1392,46 @@
1394 1392 if ( str_contains( $icon, ' ' ) ) {
1395 1393 $icon = explode( ' ', $icon );
1396 1394 $icon = reset( $icon );
1397 1395 }
1396 + }
1398 1397
1399 - $icon = '<svg class="frmsvg' . esc_attr( $class ) . '"' . $html_atts . '><use href="#' . esc_attr( $icon ) . '" /></svg>';
1400 - }
1398 + if ( $atts ) {
1399 + // A caller passed attributes, so kses still has to decide which of them survive. Its
1400 + // allowlist comes from safe_html() through the frm_striphtml_allowed_tags filter, and
1401 + // add-ons widen it around their own icons, so there is no fixed list to check against.
1402 + $html_atts = self::array_to_html_params( $atts );
1403 + $markup = $is_font_icon
1404 + ? '<i class="' . esc_attr( $class ) . '"' . $html_atts . '></i>'
1405 + : '<svg class="frmsvg' . esc_attr( $class ) . '"' . $html_atts . '><use href="#' . esc_attr( $icon ) . '" /></svg>';
1401 1406
1402 - if ( $echo ) {
1403 - echo self::kses_icon( $icon ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1407 + if ( ! $echo ) {
1408 + return $markup;
1409 + }
1410 +
1411 + echo self::kses_icon( $markup ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1404 1412 return null;
1405 1413 }
1406 1414
1407 - return $icon;
1415 + /**
1416 + * With no attributes from the caller, the tag is nothing but this method's own markup
1417 + * around an escaped class and icon id, so there is nothing left for kses to decide and it
1418 + * can be skipped. Echoing the pieces rather than a finished string keeps that safe to the
1419 + * escaping sniff without an annotation.
1420 + *
1421 + * This is the path the form builder takes for most of the tens of thousands of icons it
1422 + * renders on a large form, and the kses pass was most of what each one cost.
1423 + */
1424 + $callback = function () use ( $is_font_icon, $class, $icon ) {
1425 + if ( $is_font_icon ) {
1426 + echo '<i class="' . esc_attr( $class ) . '"></i>';
1427 + return;
1428 + }
1429 +
1430 + echo '<svg class="frmsvg' . esc_attr( $class ) . '"><use href="#' . esc_attr( $icon ) . '" /></svg>';
1431 + };
1432 +
1433 + return self::clip( $callback, $echo );
1408 1434 }
1409 1435
1410 1436 /**
1411 1437 * Run kses for icons. It needs to add a few filters first in order to preserve some custom style values.
@@ -1537,8 +1563,10 @@
1537 1563 * @param Closure $echo_function
1538 1564 * @param bool $echo
1539 1565 *
1540 1566 * @return string|null
1567 + *
1568 + * @psalm-return ($echo is true ? null : string)
1541 1569 */
1542 1570 public static function clip( $echo_function, $echo = false ) {
1543 1571 if ( ! $echo ) {
1544 1572 ob_start();
@@ -1630,9 +1658,9 @@
1630 1658
1631 1659 printf(
1632 1660 /* translators: %1$s: Start link HTML, %2$s: CTA text ("upgrading to PRO" by default), %3$s: End link HTML */
1633 1661 esc_html__( 'You\'re using Formidable Forms Lite. To unlock more features consider %1$s%2$s%3$s.', 'formidable' ),
1634 - '<a href="' . esc_url( $upgrade_link ) . '">',
1662 + '<a href="' . esc_url( $upgrade_link ) . '" target="_blank" rel="noopener">',
1635 1663 esc_html( $cta_text ),
1636 1664 '</a>'
1637 1665 );
1638 1666 ?>
@@ -2340,10 +2368,9 @@
2340 2368 if ( ! current_user_can( 'administrator' ) || current_user_can( 'frm_view_forms' ) ) {
2341 2369 return;
2342 2370 }
2343 2371
2344 - $user_id = get_current_user_id();
2345 - $user = new WP_User( $user_id );
2372 + $user = new WP_User( get_current_user_id() );
2346 2373 $frm_roles = self::frm_capabilities();
2347 2374
2348 2375 foreach ( $frm_roles as $frm_role => $frm_role_description ) {
2349 2376 $user->add_cap( $frm_role );
@@ -3067,16 +3094,23 @@
3067 3094 return $frm_settings->load_style !== 'none';
3068 3095 }
3069 3096
3070 3097 /**
3098 + * Truncate a string.
3099 + *
3100 + * Note: By default, this function will allow for a few additional characters more than $length.
3101 + * If a string has no spaces, it allows up to 50 additional characters. To force a true length limit,
3102 + * use $force_length_limit = true.
3103 + *
3071 3104 * @param mixed $original_string
3072 3105 * @param int|string $length
3073 3106 * @param int $minword
3074 3107 * @param string $continue
3108 + * @param bool $force_length_limit Force the final string to never exceed the length limit.
3075 3109 *
3076 3110 * @return string
3077 3111 */
3078 - public static function truncate( $original_string, $length, $minword = 3, $continue = '...' ) {
3112 + public static function truncate( $original_string, $length, $minword = 3, $continue = '...', $force_length_limit = false ) {
3079 3113 if ( ! is_string( $original_string ) && ! is_int( $original_string ) ) {
3080 3114 return '';
3081 3115 }
3082 3116
@@ -3090,8 +3124,13 @@
3090 3124 $original_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $str ) );
3091 3125
3092 3126 if ( $length <= 10 ) {
3093 3127 $sub = self::mb_function( array( 'mb_substr', 'substr' ), array( $str, 0, $length ) );
3128 +
3129 + if ( $force_length_limit ) {
3130 + return $sub;
3131 + }
3132 +
3094 3133 return $sub . ( $length < $original_len ? $continue : '' );
3095 3134 }
3096 3135
3097 3136 $sub = '';
@@ -3119,10 +3158,21 @@
3119 3158
3120 3159 unset( $total_len, $word );
3121 3160 }
3122 3161
3123 - $sub = self::maybe_force_truncate_on_string_with_no_spaces( $sub, $length );
3162 + $sub = self::maybe_force_truncate_on_string_with_no_spaces( $sub, $length, $force_length_limit );
3124 3163
3164 + if ( $force_length_limit ) {
3165 + // Ensure the final string doesn't exceed the length limit.
3166 + $final_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub ) );
3167 +
3168 + if ( $final_len > $length ) {
3169 + return self::mb_function( array( 'mb_substr', 'substr' ), array( $sub, 0, $length ) );
3170 + }
3171 +
3172 + return $sub;
3173 + }
3174 +
3125 3175 return $sub . ( $len < $original_len ? $continue : '' );
3126 3176 }
3127 3177
3128 3178 /**
@@ -3129,27 +3179,39 @@
3129 3179 * If the string is still too long because there may not have been any spaces, force truncate.
3130 3180 *
3131 3181 * @since 6.5.4
3132 3182 *
3133 - * @param string $sub Current substring.
3134 - * @param int $length The length limit.
3183 + * @param string $sub Current substring.
3184 + * @param int $length The length limit.
3185 + * @param bool $force_length_limit Force the string to not exceed the length limit.
3135 3186 *
3136 3187 * @return string
3137 3188 */
3138 - private static function maybe_force_truncate_on_string_with_no_spaces( $sub, $length ) {
3139 - if ( strlen( $sub ) < $length + 50 ) {
3140 - // If the string isn't way over the limit, leave it.
3141 - return $sub;
3189 + private static function maybe_force_truncate_on_string_with_no_spaces( $sub, $length, $force_length_limit = false ) {
3190 + if ( ! $force_length_limit ) {
3191 + if ( strlen( $sub ) < $length + 50 ) {
3192 + // If the string isn't way over the limit, leave it.
3193 + return $sub;
3194 + }
3195 +
3196 + $first_space = strpos( $sub, ' ', $length );
3197 +
3198 + if ( false !== $first_space ) {
3199 + // Ignore anything with spaces.
3200 + return $sub;
3201 + }
3202 +
3203 + return substr( $sub, 0, $length + 10 );
3142 3204 }
3143 3205
3144 - $first_space = strpos( $sub, ' ', $length );
3206 + // When force_length_limit is true, ensure the string doesn't exceed the length.
3207 + $final_len = self::mb_function( array( 'mb_strlen', 'strlen' ), array( $sub ) );
3145 3208
3146 - if ( false !== $first_space ) {
3147 - // Ignore anything with spaces.
3148 - return $sub;
3209 + if ( $final_len > $length ) {
3210 + return self::mb_function( array( 'mb_substr', 'substr' ), array( $sub, 0, $length ) );
3149 3211 }
3150 3212
3151 - return substr( $sub, 0, $length + 10 );
3213 + return $sub;
3152 3214 }
3153 3215
3154 3216 /**
3155 3217 * @param array $function_names
@@ -3819,10 +3881,9 @@
3819 3881 *
3820 3882 * @return void
3821 3883 */
3822 3884 public static function load_admin_wide_js( $load = true ) {
3823 - $version = self::plugin_version();
3824 - wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), $version );
3885 + wp_register_script( 'formidable_admin_global', self::plugin_url() . '/js/formidable_admin_global.js', array( 'jquery' ), self::plugin_version() );
3825 3886
3826 3887 $global_strings = array(
3827 3888 'updating_msg' => __( 'Please wait while your site updates.', 'formidable' ),
3828 3889 'deauthorize' => __( 'Are you sure you want to deauthorize Formidable Forms on this site?', 'formidable' ),
@@ -3920,8 +3981,10 @@
3920 3981 'text_shortcodes' => __( 'This calculation may have shortcodes that work in text calculations but not numeric calculations.', 'formidable' ),
3921 3982 /* translators: %d is the number of allowed actions per form */
3922 3983 'only_one_action' => sprintf( __( 'This form action is limited to %d per form.', 'formidable' ), 1 ),
3923 3984 'edit_action_text' => __( 'Please edit the existing form action.', 'formidable' ),
3985 + 'only_one_payment_action' => __( 'This form already has a payment action, and is currently limited to a single payment action.', 'formidable' ),
3986 + 'only_one_stripe_action' => __( 'This form already has a payment action. Multiple Stripe actions are available when using the Stripe add-on, available for Formidable Business licenses and higher.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3924 3987 'unsafe_params' => FrmFormsHelper::reserved_words(),
3925 3988 /* Translators: %s is the name of a Detail Page Slug that is a reserved word.*/
3926 3989 'slug_is_reserved' => sprintf( __( 'The Detail Page Slug "%s" is reserved by WordPress. This may cause problems. Is this intentional?', 'formidable' ), '****' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
3927 3990 /* 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. */
@@ -3950,8 +4013,11 @@
3950 4013 // In older versions this event listener causes the section to immediately close again
3951 4014 // When the h3 element is clicked. It's only required in WP 6.7+.
3952 4015 'requireAccordionTitleClickListener' => version_compare( $wp_version, '6.7', '>=' ),
3953 4016 );
4017 +
4018 + self::add_form_builder_modal_data( $admin_script_strings );
4019 +
3954 4020 /**
3955 4021 * @param array $admin_script_strings
3956 4022 */
3957 4023 $admin_script_strings = apply_filters( 'frm_admin_script_strings', $admin_script_strings );
@@ -3964,8 +4030,80 @@
3964 4030 }//end if
3965 4031 }
3966 4032
3967 4033 /**
4034 + * @param array $admin_script_strings
4035 + *
4036 + * @return void
4037 + */
4038 + private static function add_form_builder_modal_data( &$admin_script_strings ) {
4039 + if ( ! self::is_form_builder_page() || self::pro_is_installed() ) {
4040 + return;
4041 + }
4042 +
4043 + $stripe_connected = FrmStrpLiteConnectHelper::at_least_one_mode_is_setup();
4044 + $square_connected = FrmSquareLiteConnectHelper::at_least_one_mode_is_setup();
4045 + $paypal_connected = FrmPayPalLiteConnectHelper::at_least_one_mode_is_setup();
4046 + $gateway_connected = $stripe_connected || $square_connected || $paypal_connected;
4047 + $payments_settings_url = FrmStrpLiteAppController::get_payments_settings_url();
4048 +
4049 + if ( ! $gateway_connected ) {
4050 + // This modal shows when user clicks on one of the pricing fields and no payment gateways configured.
4051 + $admin_script_strings['paymentsSettingsModal'] = array(
4052 + 'title' => __( 'Setup a Payment Gateway first', 'formidable' ),
4053 + 'msg' => __( 'To use the payment fields, please install and configure a payment gateway in your account settings.', 'formidable' ),
4054 + 'closeText' => __( 'Close', 'formidable' ),
4055 + 'actionUrl' => $payments_settings_url,
4056 + 'actionText' => __( 'Go to Payment Settings', 'formidable' ),
4057 + 'noCenter' => true,
4058 + );
4059 + }
4060 +
4061 + // This modal shows on load once after upgrading the plugin.
4062 + $show_pricing_fields_modal = get_option( 'frm_show_pricing_fields_modal' );
4063 +
4064 + if ( ! $show_pricing_fields_modal ) {
4065 + return;
4066 + }
4067 +
4068 + $admin_script_strings['pricingFieldsModal'] = array(
4069 + 'title' => esc_html__( 'Start Accepting Payments Today!', 'formidable' ),
4070 + 'img' => esc_url( self::plugin_url() . '/images/upsell/pricing-fields.png' ),
4071 + 'noCenter' => true,
4072 + );
4073 +
4074 + if ( $gateway_connected ) {
4075 + $gateway_texts = array();
4076 +
4077 + if ( $stripe_connected ) {
4078 + $gateway_texts['stripe'] = esc_html__( 'Stripe', 'formidable' );
4079 + }
4080 +
4081 + if ( $square_connected ) {
4082 + $gateway_texts['square'] = esc_html__( 'Square', 'formidable' );
4083 + }
4084 +
4085 + if ( $paypal_connected ) {
4086 + $gateway_texts['paypal'] = esc_html__( 'PayPal', 'formidable' );
4087 + }
4088 +
4089 + $admin_script_strings['pricingFieldsModal']['msg'] = sprintf(
4090 + // translators: %s: Stripe, Square, or PayPal.
4091 + esc_html__( 'You already have %s connected, so these have already been unlocked.', 'formidable' ),
4092 + esc_html( implode( ' ' . esc_html__( 'and', 'formidable' ) . ' ', $gateway_texts ) )
4093 + );
4094 + } else {
4095 + $admin_script_strings['pricingFieldsModal']['closeText'] = __( 'I\'ll do it later!', 'formidable' );
4096 + $admin_script_strings['pricingFieldsModal']['actionText'] = __( 'Setup Payments Now', 'formidable' );
4097 + $admin_script_strings['pricingFieldsModal']['actionUrl'] = $payments_settings_url;
4098 + // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
4099 + $admin_script_strings['pricingFieldsModal']['msg'] = __( 'We\'ve unlocked Product, Quantity, and Total fields for Lite users! You can now transform your forms into checkout pages. To start collecting revenue, simply connect your preferred payment gateway (Stripe, Square, or PayPal) in your settings.', 'formidable' );
4100 + }//end if
4101 +
4102 + delete_option( 'frm_show_pricing_fields_modal' );
4103 + }
4104 +
4105 + /**
3968 4106 * Get the no label text.
3969 4107 *
3970 4108 * @since 6.25.1
3971 4109 *
@@ -4488,11 +4626,9 @@
4488 4626 *
4489 4627 * @return array
4490 4628 */
4491 4629 public static function maybe_filter_array( $values, $keys ) {
4492 - $allow_unfiltered_html = self::allow_unfiltered_html();
4493 -
4494 - if ( $allow_unfiltered_html ) {
4630 + if ( self::allow_unfiltered_html() ) {
4495 4631 return $values;
4496 4632 }
4497 4633
4498 4634 foreach ( $keys as $key ) {
@@ -4605,14 +4741,9 @@
4605 4741 * @return bool
4606 4742 */
4607 4743 public static function show_new_feature( $feature ) {
4608 4744 $link = FrmAddonsController::install_link( $feature );
4609 -
4610 - if ( array_key_exists( 'status', $link ) || array_key_exists( 'class', $link ) ) {
4611 - return true;
4612 - }
4613 -
4614 - return 'coupons' === $feature && class_exists( 'FrmCouponsAppController' );
4745 + return array_key_exists( 'status', $link ) || array_key_exists( 'class', $link );
4615 4746 }
4616 4747
4617 4748 /**
4618 4749 * Enhances upgrade data parameters with installation link and plan requirement information.
@@ -5006,13 +5137,18 @@
5006 5137 * Check if a string is valid UTF-8.
5007 5138 *
5008 5139 * @since 6.24
5009 5140 *
5010 - * @param string $string The string to check.
5141 + * @param string|null $string The string to check.
5011 5142 *
5012 5143 * @return bool
5013 5144 */
5014 5145 public static function is_valid_utf8( $string ) {
5146 + if ( is_null( $string ) ) {
5147 + // Return true so we do not attempt to change encoding on a null value.
5148 + return true;
5149 + }
5150 +
5015 5151 // wp_is_valid_utf8 is added in WP 6.9.
5016 5152 if ( function_exists( 'wp_is_valid_utf8' ) ) {
5017 5153 return wp_is_valid_utf8( $string );
5018 5154 }