: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Make field choices sortable.
* Currently used for select, radio, and checkboxes field types.
* @param {string} type Type.
* @param {string|undefined} selector Selector.
fieldChoiceSortable( type, selector ) {
selector = typeof selector !== 'undefined' ? selector : '.wpforms-field-option-' + type + ' .wpforms-field-option-row-choices ul';
$( selector ).sortable( {
const id = ui.item.parent().data( 'field-id' );
app.fieldChoiceUpdate( type, id );
$builder.trigger( 'wpformsFieldChoiceMove', ui );
update( e, ui ) { // eslint-disable-line no-unused-vars
* Generate Choice label. Used in field preview template.
* @param {Object} data Template data.
* @param {number} choiceID Choice ID.
* @return {string} Label.
fieldChoiceLabel( data, choiceID ) { // eslint-disable-line complexity
const isPaymentChoice = [ 'payment-multiple', 'payment-checkbox' ].includes( data.settings.type ),
isIconImageChoice = data.settings.choices_icons || data.settings.choices_images,
isEmptyLabel = typeof data.settings.choices[ choiceID ].label === 'undefined' || data.settings.choices[ choiceID ].label.length === 0;
// Do not set a placeholder for an empty label in Icon and Image choices except for payment fields.
if ( isEmptyLabel && ! isPaymentChoice && isIconImageChoice ) {
const placeholder = isPaymentChoice ? wpforms_builder.payment_choice_empty_label_tpl : wpforms_builder.choice_empty_label_tpl;
let label = ! isEmptyLabel
? wpf.sanitizeHTML( data.settings.choices[ choiceID ].label, wpforms_builder.allowed_label_html_tags )
: placeholder.replace( '{number}', choiceID );
if ( data.settings.show_price_after_labels ) {
label += ' - ' + wpf.amountFormatCurrency( data.settings.choices[ choiceID ].value );
* Update field choices in the preview area for the Fields panel.
* Currently used for select, radio, and checkboxes field types.
* @param {string} type Field type.
* @param {string|number} id Field ID.
* @param {number} count Number of choices to show, -1 if not set.
fieldChoiceUpdate: ( type, id, count = -1 ) => { // eslint-disable-line complexity, max-lines-per-function
const isDynamicChoices = app.dropdownField.helpers.isDynamicChoices( id );
if ( app.replaceChoicesWithTemplate( type, id, isDynamicChoices ) ) {
count = app.settings.choicesLimitLong;
// Dropdown payment choices are of select type.
if ( 'payment-select' === type ) {
const $primary = $( '#wpforms-field-' + id + ' .primary-input' );
if ( 'select' === type ) {
if ( ! isDynamicChoices ) {
newChoice = '<option value="{label}">{label}</option>';
$primary.find( 'option' ).not( '.placeholder' ).remove();
} else if ( 'radio' === type || 'checkbox' === type || 'gdpr-checkbox' === type ) {
type = 'gdpr-checkbox' === type ? 'checkbox' : type;
$primary.find( 'li' ).remove();
newChoice = '<li><input type="' + type + '" disabled>{label}</li>';
// Building an inner content for Primary field.
const $choicesList = $( '#wpforms-field-option-row-' + id + '-choices .choices-list' ),
$choicesToRender = $choicesList.find( 'li' ).slice( 0, count ),
hasDefaults = !! $choicesList.find( 'input.default:checked' ).length,
modernSelectChoices = [],
showPriceAfterLabels = $( '#wpforms-field-option-' + id + '-show_price_after_labels' ).prop( 'checked' ),
isModernSelect = app.dropdownField.helpers.isModernSelect( $primary );
$choicesToRender.get().forEach( function( item ) {// eslint-disable-line complexity
value = $this.find( 'input.value' ).val(),
choiceID = $this.data( 'key' );
let label = wpf.sanitizeHTML( $this.find( 'input.label' ).val().trim(), wpforms_builder.allowed_label_html_tags ),
label = label !== '' ? label : wpforms_builder.choice_empty_label_tpl.replace( '{number}', choiceID );
label += ( showPriceAfterLabels && value ) ? ' - ' + wpf.amountFormatCurrency( value ) : '';
if ( ! isModernSelect ) {
if ( ! isDynamicChoices ) {
$choice = $( newChoice.replace( /{label}/g, label ) );
$primary.append( $choice );
modernSelectChoices.push(
const selected = $this.find( 'input.default' ).is( ':checked' );
if ( true === selected ) {
if ( ! isModernSelect ) {
app.setClassicSelectedChoice( $choice );
modernSelectChoices[ modernSelectChoices.length - 1 ].selected = true;
$choice.find( 'input' ).prop( 'checked', 'true' );
const placeholderClass = $primary.prop( 'multiple' ) ? 'input.choices__input' : '.choices__inner .choices__placeholder',
choicesjsInstance = app.dropdownField.helpers.getInstance( $primary );
if ( ! isDynamicChoices ) {
choicesjsInstance.removeActiveItems();
choicesjsInstance.setChoices( modernSelectChoices, 'value', 'label', true );
// Re-initialize modern dropdown to properly determine and update placeholder.
app.dropdownField.helpers.update( id, isDynamicChoices );
// Hide/show a placeholder for Modern select if it has or not default choices.
.find( placeholderClass )
.toggleClass( 'wpforms-hidden', hasDefaults );
* Generate Choice label. Used in field preview template.
* @param {string} type Field type.
* @param {number} id Field ID.
* @param {boolean} isDynamicChoices Whether the field has dynamic choices.
* @return {boolean} True if the template was used.
replaceChoicesWithTemplate: ( type, id, isDynamicChoices ) => { // eslint-disable-line complexity
// Radio, Checkbox, and Payment Multiple/Checkbox use _ template.
if ( 'radio' !== type && 'checkbox' !== type && 'payment-multiple' !== type && 'payment-checkbox' !== type ) {
const order = wpf.getChoicesOrder( id ),
tmpl = wp.template( 'wpforms-field-preview-checkbox-radio-payment-multiple' );
const fieldSettings = wpf.getField( id ),
slicedOrder = order.slice( 0, app.settings.choicesLimit ),
// If Icon Choices is on, get the valid color.
if ( fieldSettings.choices_icons ) {
// eslint-disable-next-line camelcase
data.settings.choices_icons_color = app.getValidColorPickerValue( $( '#wpforms-field-option-' + id + '-choices_icons_color' ) );
// Slice choices for preview.
slicedOrder.forEach( function( entry ) {
slicedChoices[ entry ] = fieldSettings.choices[ entry ];
fieldSettings.choices = slicedChoices;
if ( 'checkbox' === type || 'payment-checkbox' === type ) {
if ( ! isDynamicChoices ) {
$( '#wpforms-field-' + id ).find( 'ul.primary-input' ).replaceWith( tmpl( data ) );
// Toggle limit choices alert message.
app.firstNChoicesAlert( id, order.length );
* Set classic selected choice.
* @param {jQuery|undefined} $choice Choice option.
setClassicSelectedChoice( $choice ) {
if ( $choice === undefined ) {
$choice.prop( 'selected', 'true' );
* Field choice bulk add toggling.
* @param {Object} el jQuery object.
fieldChoiceBulkAddToggle( el ) {
$label = $this.closest( 'label' );
if ( $this.hasClass( 'bulk-add-showing' ) ) {
// "Import details" is showing, so hide/remove it.
const $selector = $label.next( '.bulk-add-display' );
$selector.slideUp( 400, function() {
$this.find( 'span' ).text( wpforms_builder.bulk_add_show );
let importOptions = '<div class="bulk-add-display unfoldable-cont">';
importOptions += '<p class="heading wpforms-clear">' + wpforms_builder.bulk_add_heading + ' <a href="#" class="toggle-bulk-add-presets">' + wpforms_builder.bulk_add_presets_show + '</a></p>';
for ( const key in wpforms_preset_choices ) {
importOptions += '<li><a href="#" data-preset="' + key + '" class="bulk-add-preset-insert">' + wpforms_preset_choices[ key ].name + '</a></li>';
importOptions += '</ul>';
importOptions += '<textarea placeholder="' + wpforms_builder.bulk_add_placeholder + '"></textarea>';
importOptions += '<button class="bulk-add-insert wpforms-btn wpforms-btn-sm wpforms-btn-blue">' + wpforms_builder.bulk_add_button + '</button>';
importOptions += '</div>';
$label.after( importOptions );
$label.next( '.bulk-add-display' ).slideDown( 400, function() {
$( this ).find( 'textarea' ).trigger( 'focus' );
$this.find( 'span' ).text( wpforms_builder.bulk_add_hide );
$this.toggleClass( 'bulk-add-showing' );
* Field choice bulk insert the new choices.
* @param {Object} el DOM element.
fieldChoiceBulkAddInsert( el ) {
$container = $this.closest( '.wpforms-field-option-row' ),
$textarea = $container.find( 'textarea' ),
$list = $container.find( '.choices-list' ),
$choice = $list.find( 'li:first-of-type' ).clone().wrap( '<div>' ).parent();
const fieldID = $container.data( 'field-id' ),
type = $list.data( 'field-type' );
let nextID = Number( $list.attr( 'data-next-id' ) );
const newValues = $textarea.val().split( '\n' );
$this.prop( 'disabled', true ).html( $this.html() + ' ' + s.spinner );
$choice.find( 'input.value,input.label' ).attr( 'value', '' );
$choice.find( 'input.default' ).attr( 'checked', false );
$choice.find( 'input.source-icon' ).attr( 'value', wpforms_builder.icon_choices.default_icon );
$choice.find( 'input.source-icon-style' ).attr( 'value', wpforms_builder.icon_choices.default_icon_style );
$choice.find( '.ic-fa-preview' ).removeClass().addClass( `ic-fa-preview ic-fa-${ wpforms_builder.icon_choices.default_icon_style } ic-fa-${ wpforms_builder.icon_choices.default_icon }` );
$choice.find( '.ic-fa-preview + span' ).text( wpforms_builder.icon_choices.default_icon );
for ( const key in newValues ) {
if ( ! newValues.hasOwnProperty( key ) ) {
const value = wpf.sanitizeHTML( newValues[ key ] ).trim().replace( /"/g, '"' );
newChoice = newChoice.replace( /\[choices\]\[(\d+)\]/g, '[choices][' + nextID + ']' );
newChoice = newChoice.replace( /data-key="(\d+)"/g, 'data-key="' + nextID + '"' );
newChoice = newChoice.replace( /value="" class="label"/g, 'value="' + value + '" class="label"' );
// For some reason, IE has its own attribute order.
newChoice = newChoice.replace( /class="label" type="text" value=""/g, 'class="label" type="text" value="' + value + '"' );
$list.attr( 'data-next-id', nextID ).append( newChoices );
app.fieldChoiceUpdate( type, fieldID, nextID );
$builder.trigger( 'wpformsFieldChoiceAdd' );
app.fieldChoiceBulkAddToggle( $container.find( '.toggle-bulk-add-display' ) );
* Toggle fields tabs (Add Fields, Field Options.
* @param {number|string} id Field Id or `add-fields` or `field-options`.
* @return {false|void} False if event is prevented.
const event = WPFormsUtils.triggerEvent( $builder, 'wpformsFieldTabToggle', [ id ] );
// Allow callbacks on `wpformsFieldTabToggle` to cancel tab toggle by triggering `event.preventDefault()`.
if ( event.isDefaultPrevented() ) {
$( '.wpforms-tab a' ).removeClass( 'active' );
$( '.wpforms-field, .wpforms-title-desc' ).removeClass( 'active' );
if ( id === 'add-fields' ) {
$( '#add-fields a' ).addClass( 'active' );
$( '.wpforms-field-options' ).hide();
$( '.wpforms-add-fields' ).show();
$( '#field-options a' ).addClass( 'active' );
if ( id === 'field-options' ) {
const $field = $( '.wpforms-field' ).first();
$field.addClass( 'active' );
id = $field.data( 'field-id' );
$( '#wpforms-field-' + id ).addClass( 'active' );
$( '.wpforms-field-option' ).hide();
$( '#wpforms-field-option-' + id ).show();
$( '.wpforms-add-fields' ).hide();
$( '.wpforms-field-options' ).show();
$builder.trigger( 'wpformsFieldOptionTabToggle', [ id ] );
* Watches fields being added and listens for a pagebreak field.
* If a pagebreak field is added, and it's the first one, then we
* automatically add the top and bottom pagebreak elements to the
* @param {Object} event Current DOM event.
* @param {number} id Field ID.
* @param {string} type Field type.
fieldPagebreakAdd( event, id, type ) {
/* eslint-disable camelcase */
if ( 'pagebreak' !== type ) {
if ( ! s.pagebreakTop ) {
app.fieldAdd( 'pagebreak', options ).done( function( res ) {
s.pagebreakTop = res.data.field.id;
const $preview = $( '#wpforms-field-' + res.data.field.id ),
$options = $( '#wpforms-field-option-' + res.data.field.id );
$options.find( '.wpforms-field-option-group' ).addClass( 'wpforms-pagebreak-top' );
$preview.addClass( 'wpforms-field-stick wpforms-pagebreak-top' );
} else if ( ! s.pagebreakBottom ) {
s.pagebreakBottom = true;
app.fieldAdd( 'pagebreak', options ).done( function( res ) {
s.pagebreakBottom = res.data.field.id;
const $preview = $( '#wpforms-field-' + res.data.field.id ),
$options = $( '#wpforms-field-option-' + res.data.field.id );
$options.find( '.wpforms-field-option-group' ).addClass( 'wpforms-pagebreak-bottom' );
$preview.addClass( 'wpforms-field-stick wpforms-pagebreak-bottom' );
* Watches fields being deleted and listens for a pagebreak field.
* If a pagebreak field is added, and it's the first one, then we
* automatically add the top and bottom pagebreak elements to the
* @param {Object} event Current DOM event.
* @param {number} id Field ID.
* @param {string} type Field type.
fieldPagebreakDelete( event, id, type ) {
if ( 'pagebreak' !== type ) {
const pagebreaksRemaining = $( '#wpforms-panel-fields .wpforms-field-pagebreak' ).not( '.wpforms-pagebreak-top, .wpforms-pagebreak-bottom' ).length;
if ( pagebreaksRemaining ) {
// All pagebreaks, excluding top/bottom, are gone.
// So we need to remove the top and bottom pagebreak.
const $preview = $( '#wpforms-panel-fields .wpforms-preview-wrap' ),
$top = $preview.find( '.wpforms-pagebreak-top' ),
topID = $top.data( 'field-id' ),
$bottom = $preview.find( '.wpforms-pagebreak-bottom' ),
bottomID = $bottom.data( 'field-id' );
$( '#wpforms-field-option-' + topID ).remove();
$( '#wpforms-field-option-' + bottomID ).remove();
s.pagebreakBottom = false;