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 +111 -27 6.28 → 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',
@@ -346,8 +373,41 @@
346 373 return apply_filters( 'frm_pro_available_fields', $fields );
347 374 }
348 375
349 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 + /**
350 410 * Consider a field new for 90 days after the release date.
351 411 *
352 412 * @since 6.8.3
353 413 *
@@ -356,9 +416,9 @@
356 416 * @return bool
357 417 */
358 418 private static function field_is_new( $type ) {
359 419 $release_dates = array(
360 - 'coupon' => '2026-01-13',
420 + 'virtual' => '2026-03-10',
361 421 );
362 422
363 423 if ( ! isset( $release_dates[ $type ] ) ) {
364 424 return false;
@@ -369,15 +429,31 @@
369 429 return gmdate( 'Y-m-d' ) < $three_months_after_release;
370 430 }
371 431
372 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 + /**
373 450 * @since 4.0
374 451 *
375 452 * @return array
376 453 */
377 454 public static function all_field_selection() {
378 - $pro_field_selection = self::pro_field_selection();
379 - return array_merge( $pro_field_selection, self::field_selection() );
455 + return array_merge( self::pro_field_selection(), self::field_selection() );
380 456 }
381 457
382 458 /**
383 459 * Create a field.
@@ -401,13 +477,19 @@
401 477 $new_values[ $col ] = $values[ $col ];
402 478 }
403 479 }
404 480
405 - $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() );
406 488 $new_values['field_order'] = isset( $values['field_order'] ) ? (int) $values['field_order'] : null;
407 489 $new_values['required'] = isset( $values['required'] ) ? (int) $values['required'] : 0;
408 490 $new_values['form_id'] = isset( $values['form_id'] ) ? (int) $values['form_id'] : null;
409 - $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();
410 492 $new_values['created_at'] = current_time( 'mysql', 1 );
411 493
412 494 if ( isset( $values['id'] ) ) {
413 495 $frm_duplicate_ids[ $values['field_key'] ] = $new_values['field_key'];
@@ -450,9 +532,9 @@
450 532 * See FrmAppHelper::allow_unfiltered_html.
451 533 *
452 534 * @since 5.0.08
453 535 *
454 - * @param array $options
536 + * @param array|string $options Already serialized for choice fields, and an empty string for fields with no choices.
455 537 *
456 538 * @return array
457 539 */
458 540 private static function maybe_filter_options( $options ) {
@@ -502,13 +584,9 @@
502 584
503 585 $safe_atts = array();
504 586
505 587 foreach ( $attr as $attr_key => $att ) {
506 - if ( ! is_numeric( $attr_key ) ) {
507 - // opt=1 without parentheses for example is mapped like 'opt' => 1.
508 - $key = $attr_key;
509 - $value = $att;
510 - } else {
588 + if ( is_numeric( $attr_key ) ) {
511 589 // Some data is mapped like 0 => 'placeholder="Placeholder"'.
512 590 $split = explode( '=', $att, 2 );
513 591
514 592 if ( 2 !== count( $split ) ) {
@@ -516,8 +594,12 @@
516 594 }
517 595
518 596 $key = trim( $split[0] );
519 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;
520 602 }
521 603
522 604 if ( FrmAppHelper::input_key_is_safe( $key, 'update' ) ) {
523 605 $safe_atts[ $key ] = $value;
@@ -689,15 +771,17 @@
689 771 }
690 772
691 773 // Serialize array values
692 774 foreach ( array( 'field_options', 'options' ) as $opt ) {
693 - if ( isset( $values[ $opt ] ) && is_array( $values[ $opt ] ) ) {
694 - if ( 'field_options' === $opt ) {
695 - $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
696 - }
775 + if ( ! isset( $values[ $opt ] ) || ! is_array( $values[ $opt ] ) ) {
776 + continue;
777 + }
697 778
698 - $values[ $opt ] = serialize( $values[ $opt ] );
779 + if ( 'field_options' === $opt ) {
780 + $values[ $opt ] = self::maybe_filter_options( $values[ $opt ] );
699 781 }
782 +
783 + $values[ $opt ] = serialize( $values[ $opt ] );
700 784 }
701 785
702 786 if ( isset( $values['default_value'] ) && is_array( $values['default_value'] ) ) {
703 787 $values['default_value'] = json_encode( $values['default_value'] );
@@ -1000,17 +1084,19 @@
1000 1084 *
1001 1085 * @return void
1002 1086 */
1003 1087 private static function maybe_include_repeating_fields( $inc_repeat, &$where ) {
1004 - if ( $inc_repeat === 'include' ) {
1005 - $form_id = $where['fi.form_id'];
1006 - $where[] = array(
1007 - 'or' => 1,
1008 - 'fi.form_id' => $form_id,
1009 - 'fr.parent_form_id' => $form_id,
1010 - );
1011 - unset( $where['fi.form_id'] );
1088 + if ( $inc_repeat !== 'include' ) {
1089 + return;
1012 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'] );
1013 1099 }
1014 1100
1015 1101 /**
1016 1102 * @param array $results
@@ -1351,11 +1437,9 @@
1351 1437 *
1352 1438 * @return bool
1353 1439 */
1354 1440 public static function is_multiple_select( $field ) {
1355 - $field_type = self::get_field_type( $field );
1356 - $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden';
1357 -
1441 + $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && self::get_field_type( $field ) !== 'hidden';
1358 1442 return apply_filters( 'frm_is_multiple_select', $is_multiple, $field );
1359 1443 }
1360 1444
1361 1445 /**