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/models/FrmField.php +148 -66 6.27 → trunk View file →
@@ -79,8 +79,12 @@
79 79 'credit_card' => array(
80 80 'name' => __( 'Payment', 'formidable' ),
81 81 'icon' => 'frmfont frm_credit_card2_icon',
82 82 ),
83 + 'address' => array(
84 + 'name' => __( 'Address', 'formidable' ),
85 + 'icon' => 'frmfont frm_location2_icon',
86 + ),
83 87 FrmSubmitHelper::FIELD_TYPE => array(
84 88 'name' => __( 'Submit', 'formidable' ),
85 89 'hide' => true,
86 90 ),
@@ -88,8 +92,23 @@
88 92 'name' => __( 'GDPR', 'formidable' ),
89 93 'icon' => 'frmfont frm-gdpr-icon',
90 94 'hide' => FrmFieldGdprHelper::hide_gdpr_field(),
91 95 ),
96 + 'product' => array(
97 + 'name' => __( 'Product', 'formidable' ),
98 + 'icon' => 'frmfont frm_product2_icon',
99 + 'section' => 'pricing',
100 + ),
101 + 'quantity' => array(
102 + 'name' => __( 'Quantity', 'formidable' ),
103 + 'icon' => 'frmfont frm_quantity_icon',
104 + 'section' => 'pricing',
105 + ),
106 + 'total' => array(
107 + 'name' => __( 'Total', 'formidable' ),
108 + 'icon' => 'frmfont frm_total2_icon',
109 + 'section' => 'pricing',
110 + ),
92 111 );
93 112
94 113 /**
95 114 * @param array $fields
@@ -301,8 +320,16 @@
301 320 'link' => 'https://simplyscheduleappointments.com/meet/formidable/',
302 321 'upsell_image' => $upsell_images_url . 'appointment-field-preview.webp',
303 322 'learn-more' => 'simply-schedule-appointments-forms',
304 323 ),
324 + 'virtual' => array(
325 + 'name' => __( 'Virtual', 'formidable' ),
326 + 'icon' => 'frmfont frm-virtual-field-icon',
327 + 'message' => esc_html__( 'Protect sensitive data by storing field values server-side only, preventing users from viewing or manipulating them in their browser.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
328 + 'upsell_image' => $upsell_images_url . 'virtual-field-preview.webp',
329 + 'learn-more' => '/virtual',
330 + 'is_new' => self::field_is_new( 'virtual' ),
331 + ),
305 332 'product' => array(
306 333 'name' => __( 'Product', 'formidable' ),
307 334 'icon' => 'frmfont frm_product2_icon',
308 335 'section' => 'pricing',
@@ -325,12 +352,9 @@
325 352 'message' => __( 'Turn your form into a simple storefront by adding fields to list products, choose quantities, and calculate totals, enabling direct sales.', 'formidable' ), // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
326 353 'upsell_image' => $upsell_images_url . 'total-field-preview.webp',
327 354 'learn-more' => 'features/pricing-fields',
328 355 ),
329 - );
330 -
331 - if ( FrmAppHelper::show_new_feature( 'coupons' ) ) {
332 - $fields['coupon'] = array(
356 + 'coupon' => array(
333 357 'name' => __( 'Coupon', 'formidable' ),
334 358 'icon' => 'frm_icon_font frm_coupon_icon frm_show_upgrade',
335 359 'addon' => 'coupons',
336 360 'section' => 'pricing',
@@ -335,10 +359,10 @@
335 359 'addon' => 'coupons',
336 360 'section' => 'pricing',
337 361 'limit' => 1,
338 362 'is_new' => self::field_is_new( 'coupon' ),
339 - );
340 - }
363 + ),
364 + );
341 365
342 366 // Since the signature field may be in a different section, don't show it twice.
343 367 $lite_fields = self::field_selection();
344 368
@@ -349,8 +373,41 @@
349 373 return apply_filters( 'frm_pro_available_fields', $fields );
350 374 }
351 375
352 376 /**
377 + * Flag Pro field types that require a newer Pro version with frm_show_update.
378 + *
379 + * @since 6.29
380 + *
381 + * @param array $fields Available Pro field types.
382 + *
383 + * @return array
384 + */
385 + public static function show_update_for_pro_fields( $fields ) {
386 + if ( FrmAppHelper::pro_is_installed() && ! class_exists( 'FrmProVirtualFieldController', false ) ) {
387 + $fields['virtual']['icon'] .= ' frm_show_update';
388 + }
389 +
390 + return $fields;
391 + }
392 +
393 + /**
394 + * Gets field section labels in the form builder.
395 + *
396 + * @since 6.30
397 + *
398 + * @return array
399 + */
400 + public static function field_section_labels() {
401 + return apply_filters(
402 + 'frm_available_field_sections',
403 + array(
404 + 'pricing' => __( 'Pricing Fields', 'formidable' ),
405 + )
406 + );
407 + }
408 +
409 + /**
353 410 * Consider a field new for 90 days after the release date.
354 411 *
355 412 * @since 6.8.3
356 413 *
@@ -359,9 +416,9 @@
359 416 * @return bool
360 417 */
361 418 private static function field_is_new( $type ) {
362 419 $release_dates = array(
363 - 'coupon' => '2026-01-13',
420 + 'virtual' => '2026-03-10',
364 421 );
365 422
366 423 if ( ! isset( $release_dates[ $type ] ) ) {
367 424 return false;
@@ -372,15 +429,31 @@
372 429 return gmdate( 'Y-m-d' ) < $three_months_after_release;
373 430 }
374 431
375 432 /**
433 + * Remove field types that are moved from Pro to Lite.
434 + *
435 + * @since 6.30
436 + *
437 + * @param array $pro_fields
438 + *
439 + * @return void
440 + */
441 + public static function remove_moved_field_types_from_pro( &$pro_fields ) {
442 + unset( $pro_fields['credit_card'] );
443 + unset( $pro_fields['product'] );
444 + unset( $pro_fields['quantity'] );
445 + unset( $pro_fields['total'] );
446 + unset( $pro_fields['address'] );
447 + }
448 +
449 + /**
376 450 * @since 4.0
377 451 *
378 452 * @return array
379 453 */
380 454 public static function all_field_selection() {
381 - $pro_field_selection = self::pro_field_selection();
382 - return array_merge( $pro_field_selection, self::field_selection() );
455 + return array_merge( self::pro_field_selection(), self::field_selection() );
383 456 }
384 457
385 458 /**
386 459 * Create a field.
@@ -404,13 +477,19 @@
404 477 $new_values[ $col ] = $values[ $col ];
405 478 }
406 479 }
407 480
408 - $new_values['options'] = self::maybe_filter_options( $values['options'] );
481 + // Both option columns are stored serialized, but they do not accept the same shapes.
482 + // Choice fields pass options already serialized, as does FrmFieldsHelper::fill_field
483 + // when a field is duplicated, and fields with no choices pass an empty string, so a
484 + // missing key is the only thing worth defaulting. An is_array check here would throw
485 + // every one of those defaults away. field_options is always an array, so anything
486 + // else is invalid input, and replacing it stops consumers getting null.
487 + $new_values['options'] = self::maybe_filter_options( $values['options'] ?? array() );
409 488 $new_values['field_order'] = isset( $values['field_order'] ) ? (int) $values['field_order'] : null;
410 489 $new_values['required'] = isset( $values['required'] ) ? (int) $values['required'] : 0;
411 490 $new_values['form_id'] = isset( $values['form_id'] ) ? (int) $values['form_id'] : null;
412 - $new_values['field_options'] = $values['field_options'];
491 + $new_values['field_options'] = isset( $values['field_options'] ) && is_array( $values['field_options'] ) ? $values['field_options'] : array();
413 492 $new_values['created_at'] = current_time( 'mysql', 1 );
414 493
415 494 if ( isset( $values['id'] ) ) {
416 495 $frm_duplicate_ids[ $values['field_key'] ] = $new_values['field_key'];
@@ -432,14 +511,15 @@
432 511 return false;
433 512 }
434 513
435 514 self::delete_form_transient( $new_values['form_id'] );
436 - $new_id = $wpdb->insert_id;
437 515
438 516 if ( ! $return ) {
439 517 return false;
440 518 }
441 519
520 + $new_id = $wpdb->insert_id;
521 +
442 522 if ( isset( $values['id'] ) ) {
443 523 $frm_duplicate_ids[ $values['id'] ] = $new_id;
444 524 }
445 525
@@ -452,9 +532,9 @@
452 532 * See FrmAppHelper::allow_unfiltered_html.
453 533 *
454 534 * @since 5.0.08
455 535 *
456 - * @param array $options
536 + * @param array|string $options Already serialized for choice fields, and an empty string for fields with no choices.
457 537 *
458 538 * @return array
459 539 */
460 540 private static function maybe_filter_options( $options ) {
@@ -504,13 +584,9 @@
504 584
505 585 $safe_atts = array();
506 586
507 587 foreach ( $attr as $attr_key => $att ) {
508 - if ( ! is_numeric( $attr_key ) ) {
509 - // opt=1 without parentheses for example is mapped like 'opt' => 1.
510 - $key = $attr_key;
511 - $value = $att;
512 - } else {
588 + if ( is_numeric( $attr_key ) ) {
513 589 // Some data is mapped like 0 => 'placeholder="Placeholder"'.
514 590 $split = explode( '=', $att, 2 );
515 591
516 592 if ( 2 !== count( $split ) ) {
@@ -518,8 +594,12 @@
518 594 }
519 595
520 596 $key = trim( $split[0] );
521 597 $value = trim( $split[1], '"' );
598 + } else {
599 + // opt=1 without parentheses for example is mapped like 'opt' => 1.
600 + $key = $attr_key;
601 + $value = $att;
522 602 }
523 603
524 604 if ( FrmAppHelper::input_key_is_safe( $key, 'update' ) ) {
525 605 $safe_atts[ $key ] = $value;
@@ -614,9 +694,9 @@
614 694 FrmFieldsHelper::fill_field( $values, $field, $form_id, $new_key );
615 695
616 696 // If this is a repeating section, create new form
617 697 if ( self::is_repeating_field( $field ) ) {
618 - // create the repeatable form
698 + // Create the repeatable form
619 699 $new_repeat_form_id = apply_filters(
620 700 'frm_create_repeat_form',
621 701 0,
622 702 array(
@@ -684,22 +764,24 @@
684 764 */
685 765 $values = apply_filters( 'frm_clean_' . $values['type'] . '_field_options_before_update', $values, $id );
686 766
687 767 if ( $values['type'] === 'hidden' && isset( $values['field_options'] ) && isset( $values['field_options']['clear_on_focus'] ) ) {
688 - // don't keep the old placeholder setting for hidden fields
768 + // Don't keep the old placeholder setting for hidden fields
689 769 $values['field_options']['clear_on_focus'] = 0;
690 770 }
691 771 }
692 772
693 - // serialize array values
773 + // Serialize array values
694 774 foreach ( array( 'field_options', 'options' ) as $opt ) {
695 - if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
696 - if ( 'field_options' === $opt ) {
697 - $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
698 - }
775 + if ( ! isset( $values[ $opt ] ) || ! is_array( $values[ $opt ] ) ) {
776 + continue;
777 + }
699 778
700 - $values[ $opt ] = serialize( $values[ $opt ] );
779 + if ( 'field_options' === $opt ) {
780 + $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
701 781 }
782 +
783 + $values[ $opt ] = serialize( $values[ $opt ] );
702 784 }
703 785
704 786 if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
705 787 $values['default_value'] = json_encode( $values['default_value'] );
@@ -1002,17 +1084,19 @@
1002 1084 *
1003 1085 * @return void
1004 1086 */
1005 1087 private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
1006 - if ( $inc_repeat === 'include' ) {
1007 - $form_id = $where['fi.form_id'];
1008 - $where[] = array(
1009 - 'or' => 1,
1010 - 'fi.form_id' => $form_id,
1011 - 'fr.parent_form_id' => $form_id,
1012 - );
1013 - unset( $where['fi.form_id'] );
1088 + if ( $inc_repeat !== 'include' ) {
1089 + return;
1014 1090 }
1091 +
1092 + $form_id = $where['fi.form_id'];
1093 + $where[] = array(
1094 + 'or' => 1,
1095 + 'fi.form_id' => $form_id,
1096 + 'fr.parent_form_id' => $form_id,
1097 + );
1098 + unset( $where['fi.form_id'] );
1015 1099 }
1016 1100
1017 1101 /**
1018 1102 * @param array $results
@@ -1022,9 +1106,9 @@
1022 1106 *
1023 1107 * @return void
1024 1108 */
1025 1109 public static function include_sub_fields( &$results, $inc_embed, $type = 'all', $form_id = '' ) {
1026 - $no_sub_forms = empty( $results ) && $type === 'all';
1110 + $no_sub_forms = ! $results && $type === 'all';
1027 1111
1028 1112 if ( 'include' !== $inc_embed || $no_sub_forms ) {
1029 1113 return;
1030 1114 }
@@ -1029,9 +1113,9 @@
1029 1113 return;
1030 1114 }
1031 1115
1032 1116 $form_fields = $results;
1033 - $should_get_subforms = $type !== 'all' && $type !== 'form' && ! empty( $form_id );
1117 + $should_get_subforms = $type !== 'all' && $type !== 'form' && $form_id;
1034 1118
1035 1119 if ( $should_get_subforms ) {
1036 1120 $form_fields = self::get_all_types_in_form( $form_id, 'form' );
1037 1121 }
@@ -1069,9 +1153,9 @@
1069 1153 public static function getAll( $where = array(), $order_by = '', $limit = '', $blog_id = false ) {
1070 1154 $cache_key = FrmAppHelper::maybe_json_encode( $where ) . $order_by . 'l' . $limit . 'b' . $blog_id;
1071 1155
1072 1156 if ( self::$use_cache ) {
1073 - // make sure old cache doesn't get saved as a transient
1157 + // Make sure old cache doesn't get saved as a transient
1074 1158 $results = wp_cache_get( $cache_key, 'frm_field' );
1075 1159
1076 1160 if ( false !== $results ) {
1077 1161 return wp_unslash( $results );
@@ -1103,9 +1187,9 @@
1103 1187 'limit' => $limit,
1104 1188 );
1105 1189 $results = FrmDb::get_var( $table_name . ' fi JOIN ' . $form_table_name . ' fr ON fi.form_id=fr.id', $where, 'fi.*, fr.name as form_name', $args, '', $query_type );
1106 1190 } else {
1107 - // if the query is not an array, then it has already been prepared
1191 + // If the query is not an array, then it has already been prepared
1108 1192 $query .= FrmDb::prepend_and_or_where( ' WHERE ', $where ) . $order_by . $limit;
1109 1193
1110 1194 $function_name = $query_type === 'row' ? 'get_row' : 'get_results';
1111 1195 $results = $wpdb->$function_name( $query );
@@ -1185,14 +1269,16 @@
1185 1269 $before = $results->default_value;
1186 1270
1187 1271 $field_object = FrmFieldFactory::get_field_type( $results->type );
1188 1272
1189 - if ( $field_object->should_unserialize_value() ) {
1190 - FrmAppHelper::unserialize_or_decode( $results->default_value );
1273 + if ( ! $field_object->should_unserialize_value() ) {
1274 + return;
1275 + }
1191 1276
1192 - if ( $before === $results->default_value && is_string( $before ) && str_starts_with( $before, '["' ) ) {
1193 - $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
1194 - }
1277 + FrmAppHelper::unserialize_or_decode( $results->default_value );
1278 +
1279 + if ( $before === $results->default_value && is_string( $before ) && str_starts_with( $before, '["' ) ) {
1280 + $results->default_value = FrmAppHelper::maybe_json_decode( $results->default_value );
1195 1281 }
1196 1282 }
1197 1283
1198 1284 /**
@@ -1227,17 +1313,21 @@
1227 1313 private static function get_next_transient( &$fields, $base_name, $next = 0 ) {
1228 1314 $name = $next ? $base_name . $next : $base_name;
1229 1315 $next_fields = get_transient( $name );
1230 1316
1231 - if ( $next_fields ) {
1232 - $fields = array_merge( $fields, $next_fields );
1317 + if ( ! $next_fields ) {
1318 + return;
1319 + }
1233 1320
1234 - if ( count( $next_fields ) >= self::$transient_size ) {
1235 - // if this transient is full, check for another
1236 - ++$next;
1237 - self::get_next_transient( $fields, $base_name, $next );
1238 - }
1321 + $fields = array_merge( $fields, $next_fields );
1322 +
1323 + if ( count( $next_fields ) < self::$transient_size ) {
1324 + return;
1239 1325 }
1326 +
1327 + // If this transient is full, check for another
1328 + ++$next;
1329 + self::get_next_transient( $fields, $base_name, $next );
1240 1330 }
1241 1331
1242 1332 /**
1243 1333 * Save the transients in chunks for large forms
@@ -1259,11 +1349,11 @@
1259 1349 $name = $next ? $base_name . $next : $base_name;
1260 1350 $set = set_transient( $name, $field, 60 * 60 * 6 );
1261 1351
1262 1352 if ( ! $set ) {
1263 - // the transient didn't save
1353 + // The transient didn't save
1264 1354 if ( $name !== $base_name ) {
1265 - // if the first saved an others fail, this will show an incomplete form
1355 + // If the first saved an others fail, this will show an incomplete form
1266 1356 self::delete_form_transient( $form_id );
1267 1357 }
1268 1358
1269 1359 return;
@@ -1304,11 +1394,9 @@
1304 1394 }
1305 1395
1306 1396 $field_type = self::get_original_field_type( $field );
1307 1397
1308 - return self::is_checkbox( $field ) ||
1309 - $field_type === 'address' ||
1310 - self::is_multiple_select( $field );
1398 + return self::is_checkbox( $field ) || $field_type === 'address' || self::is_multiple_select( $field );
1311 1399 }
1312 1400
1313 1401 /**
1314 1402 * @since 3.0
@@ -1333,9 +1421,9 @@
1333 1421 $original_type = self::get_option( $field, 'original_type' );
1334 1422
1335 1423 if ( $original_type && $original_type !== $field_type ) {
1336 1424 // Check the original type for arrays.
1337 - $field_type = $original_type;
1425 + return $original_type;
1338 1426 }
1339 1427
1340 1428 return $field_type;
1341 1429 }
@@ -1349,11 +1437,9 @@
1349 1437 *
1350 1438 * @return bool
1351 1439 */
1352 1440 public static function is_multiple_select( $field ) {
1353 - $field_type = self::get_field_type( $field );
1354 - $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
1355 -
1441 + $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && self::get_field_type( $field ) !== 'hidden';
1356 1442 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
1357 1443 }
1358 1444
1359 1445 /**
@@ -1585,15 +1671,11 @@
1585 1671 *
1586 1672 * @return bool true if field type is checkbox or Dynamic checkbox
1587 1673 */
1588 1674 public static function is_field_type( $field, $is_type ) {
1589 - $field_type = self::get_original_field_type( $field );
1590 - $data_type = self::get_option( $field, 'data_type' );
1591 -
1592 - $is_field_type = $is_type === $field_type ||
1593 - ( 'data' === $field_type && $is_type === $data_type ) ||
1594 - ( 'lookup' === $field_type && $is_type === $data_type ) ||
1595 - ( 'product' === $field_type && $is_type === $data_type );
1675 + $field_type = self::get_original_field_type( $field );
1676 + $data_type = self::get_option( $field, 'data_type' );
1677 + $is_field_type = $is_type === $field_type || ( 'data' === $field_type && $is_type === $data_type ) || ( 'lookup' === $field_type && $is_type === $data_type ) || ( 'product' === $field_type && $is_type === $data_type ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong
1596 1678
1597 1679 /**
1598 1680 * When a field type is checked, allow individual fields
1599 1681 * to set the type.