| @@ -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 |
| @@ -307,8 +326,9 @@ | ||
| 307 | 326 | 'icon' => 'frmfont frm-virtual-field-icon', |
| 308 | 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 |
| 309 | 328 | 'upsell_image' => $upsell_images_url . 'virtual-field-preview.webp', |
| 310 | 329 | 'learn-more' => '/virtual', |
| 330 | + 'is_new' => self::field_is_new( 'virtual' ), | |
| 311 | 331 | ), |
| 312 | 332 | 'product' => array( |
| 313 | 333 | 'name' => __( 'Product', 'formidable' ), |
| 314 | 334 | 'icon' => 'frmfont frm_product2_icon', |
| @@ -370,8 +390,24 @@ | ||
| 370 | 390 | return $fields; |
| 371 | 391 | } |
| 372 | 392 | |
| 373 | 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 | + /** | |
| 374 | 410 | * Consider a field new for 90 days after the release date. |
| 375 | 411 | * |
| 376 | 412 | * @since 6.8.3 |
| 377 | 413 | * |
| @@ -380,9 +416,9 @@ | ||
| 380 | 416 | * @return bool |
| 381 | 417 | */ |
| 382 | 418 | private static function field_is_new( $type ) { |
| 383 | 419 | $release_dates = array( |
| 384 | - 'coupon' => '2026-01-13', | |
| 420 | + 'virtual' => '2026-03-10', | |
| 385 | 421 | ); |
| 386 | 422 | |
| 387 | 423 | if ( ! isset( $release_dates[ $type ] ) ) { |
| 388 | 424 | return false; |
| @@ -393,15 +429,31 @@ | ||
| 393 | 429 | return gmdate( 'Y-m-d' ) < $three_months_after_release; |
| 394 | 430 | } |
| 395 | 431 | |
| 396 | 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 | + /** | |
| 397 | 450 | * @since 4.0 |
| 398 | 451 | * |
| 399 | 452 | * @return array |
| 400 | 453 | */ |
| 401 | 454 | public static function all_field_selection() { |
| 402 | - $pro_field_selection = self::pro_field_selection(); | |
| 403 | - return array_merge( $pro_field_selection, self::field_selection() ); | |
| 455 | + return array_merge( self::pro_field_selection(), self::field_selection() ); | |
| 404 | 456 | } |
| 405 | 457 | |
| 406 | 458 | /** |
| 407 | 459 | * Create a field. |
| @@ -425,13 +477,19 @@ | ||
| 425 | 477 | $new_values[ $col ] = $values[ $col ]; |
| 426 | 478 | } |
| 427 | 479 | } |
| 428 | 480 | |
| 429 | - $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() ); | |
| 430 | 488 | $new_values['field_order'] = isset( $values['field_order'] ) ? (int) $values['field_order'] : null; |
| 431 | 489 | $new_values['required'] = isset( $values['required'] ) ? (int) $values['required'] : 0; |
| 432 | 490 | $new_values['form_id'] = isset( $values['form_id'] ) ? (int) $values['form_id'] : null; |
| 433 | - $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(); | |
| 434 | 492 | $new_values['created_at'] = current_time( 'mysql', 1 ); |
| 435 | 493 | |
| 436 | 494 | if ( isset( $values['id'] ) ) { |
| 437 | 495 | $frm_duplicate_ids[ $values['field_key'] ] = $new_values['field_key']; |
| @@ -474,9 +532,9 @@ | ||
| 474 | 532 | * See FrmAppHelper::allow_unfiltered_html. |
| 475 | 533 | * |
| 476 | 534 | * @since 5.0.08 |
| 477 | 535 | * |
| 478 | - * @param array $options | |
| 536 | + * @param array|string $options Already serialized for choice fields, and an empty string for fields with no choices. | |
| 479 | 537 | * |
| 480 | 538 | * @return array |
| 481 | 539 | */ |
| 482 | 540 | private static function maybe_filter_options( $options ) { |
| @@ -1379,11 +1437,9 @@ | ||
| 1379 | 1437 | * |
| 1380 | 1438 | * @return bool |
| 1381 | 1439 | */ |
| 1382 | 1440 | public static function is_multiple_select( $field ) { |
| 1383 | - $field_type = self::get_field_type( $field ); | |
| 1384 | - $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && $field_type !== 'hidden'; | |
| 1385 | - | |
| 1441 | + $is_multiple = self::is_option_true( $field, 'multiple' ) && self::is_field_type( $field, 'select' ) && self::get_field_type( $field ) !== 'hidden'; | |
| 1386 | 1442 | return apply_filters( 'frm_is_multiple_select', $is_multiple, $field ); |
| 1387 | 1443 | } |
| 1388 | 1444 | |
| 1389 | 1445 | /** |