: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( addButton.hasClass( 'wpforms-entry-preview-adding' ) ) {
const $fields = $( '#wpforms-panel-fields .wpforms-field-wrap > .wpforms-field' ),
position = options?.position ? options.position : $fields.length,
needPageBreakBefore = app.isEntryPreviewFieldRequiresPageBreakBefore( $fields, position ),
needPageBreakAfter = app.isEntryPreviewFieldRequiresPageBreakAfter( $fields, position );
addButton.addClass( 'wpforms-entry-preview-adding' );
if ( ! needPageBreakBefore && ! needPageBreakAfter ) {
app.fieldAdd( 'entry-preview', options ).done( function( res ) {
app.lockEntryPreviewFieldsPosition( res.data.field.id );
if ( needPageBreakBefore ) {
app.addPageBreakAndEntryPreviewFields( options, position );
app.addEntryPreviewAndPageBreakFields( options, position );
* Add the entry preview field after the page break field.
* We should wait for the page break adding to avoid id duplication.
* @param {Object} options Field options.
addEntryPreviewFieldAfterPageBreak( options ) {
const checkExist = setInterval( function() {
if ( $( '#wpforms-panel-fields .wpforms-field-wrap' ).find( '.wpforms-pagebreak-bottom, .wpforms-pagebreak-top' ).length === 2 ) {
app.fieldAdd( 'entry-preview', options ).done( function( res ) {
app.lockEntryPreviewFieldsPosition( res.data.field.id );
clearInterval( checkExist );
* Add the entry preview field after the page break field.
* @param {Object} options Field options.
* @param {number} position The field position.
addPageBreakAndEntryPreviewFields( options, position ) {
const hasPageBreak = $( '#wpforms-panel-fields .wpforms-field-wrap > .wpforms-field-pagebreak' ).length >= 3;
app.fieldAdd( 'pagebreak', { position } ).done( function( res ) {
options.position = hasPageBreak ? position + 1 : position + 2;
app.addEntryPreviewFieldAfterPageBreak( options );
const $pageBreakOptions = $( '#wpforms-field-option-' + res.data.field.id ),
$pageBreakPrevToggle = $pageBreakOptions.find( '.wpforms-field-option-row-prev_toggle' ),
$pageBreakPrevToggleField = $pageBreakPrevToggle.find( 'input' );
$pageBreakPrevToggleField.attr( 'checked', 'checked' ).trigger( 'change' );
$pageBreakPrevToggle.addClass( 'wpforms-entry-preview-block' );
* @param {string} id Field id.
const $field = $( `#wpforms-field-${ id }` );
if ( $field.hasClass( 'no-duplicate' ) ) {
title: wpforms_builder.field_locked,
content: wpforms_builder.field_locked_no_duplicate_msg,
icon: 'fa fa-info-circle',
text: wpforms_builder.close,
content: wpforms_builder.duplicate_confirm,
icon: 'fa fa-exclamation-circle',
text: wpforms_builder.ok,
// Disable the current button to avoid firing multiple click events.
// By default, "jconfirm" tends to destroy any modal DOM element upon button click.
this.$$confirm.prop( 'disabled', true );
const beforeEvent = WPFormsUtils.triggerEvent( $builder, 'wpformsBeforeFieldDuplicate', [ id, $field ] );
// Allow callbacks on `wpformsFieldBeforeDuplicate` to cancel field duplication.
if ( beforeEvent.isDefaultPrevented() ) {
const newFieldId = app.fieldDuplicateRoutine( id ),
$newField = $( `#wpforms-field-${ newFieldId }` );
// Lastly, update the next ID stored in the database.
app.increaseNextFieldIdAjaxRequest();
WPFormsUtils.triggerEvent( $builder, 'wpformsFieldDuplicated', [ id, $field, newFieldId, $newField ] );
text: wpforms_builder.cancel,
* Update the next ID stored in the database.
increaseNextFieldIdAjaxRequest() {
/* eslint-disable camelcase */
wpforms_builder.ajax_url,
field_id: elements.$nextFieldId.val(),
nonce: wpforms_builder.nonce,
action: 'wpforms_builder_increase_next_field_id',
* Duplicate field routine.
* @param {number|string} id Field ID.
* @return {number} New field ID.
fieldDuplicateRoutine( id ) { // eslint-disable-line max-lines-per-function, complexity
const $field = $( `#wpforms-field-${ id }` ),
$fieldOptions = $( `#wpforms-field-option-${ id }` ),
$fieldActive = elements.$sortableFieldsWrap.find( '>.active' ),
$visibleOptions = elements.$fieldOptions.find( '>:visible' ),
$visibleTab = $visibleOptions.find( '>.active' ),
type = $field.data( 'field-type' ),
fieldOptionsClass = $fieldOptions.attr( 'class' ),
isModernDropdown = app.dropdownField.helpers.isModernSelect( $field.find( '> .choices .primary-input' ) );
// Restore tooltips before cloning.
wpf.restoreTooltips( $fieldOptions );
// Force Modern Dropdown conversion to classic before cloning.
if ( isModernDropdown ) {
app.dropdownField.helpers.convertModernToClassic( id );
let newFieldOptions = $fieldOptions.html();
const $newField = $field.clone(),
newFieldID = parseInt( elements.$nextFieldId.val(), 10 ),
$fieldLabel = $( `#wpforms-field-option-${ id }-label` ),
fieldLabelVal = $fieldLabel.length ? $fieldLabel.val() : $( `#wpforms-field-option-${ id }-name` ).val(),
const newFieldLabel = fieldLabelVal !== ''
? `${ fieldLabelVal } ${ wpforms_builder.duplicate_copy }`
: `${ wpforms_builder.field } #${ id } ${ wpforms_builder.duplicate_copy }`;
regex.fieldOptionsID = new RegExp( 'ID #' + id, 'g' );
regex.fieldID = new RegExp( 'fields\\[' + id + '\\]', 'g' );
regex.dataFieldID = new RegExp( 'data-field-id="' + id + '"', 'g' );
regex.referenceID = new RegExp( 'data-reference="' + id + '"', 'g' );
regex.elementID = new RegExp( '\\b(id|for)="wpforms-(.*?)' + id + '(.*?)"', 'ig' );
// Toggle visibility states.
$field.after( $newField );
$fieldActive.removeClass( 'active' );
$newField.addClass( 'active' ).attr( {
id: `wpforms-field-${ newFieldID }`,
'data-field-id': newFieldID,
// Various regexes to adjust the field options to work with the new field ID.
regex.elementIdReplace = function( match, p1, p2, p3, offset, string ) { // eslint-disable-line no-unused-vars
return `${ p1 }="wpforms-${ p2 }${ newFieldID }${ p3 }"`;
newFieldOptions = newFieldOptions.replace( regex.fieldOptionsID, `ID #${ newFieldID }` );
newFieldOptions = newFieldOptions.replace( regex.fieldID, `fields[${ newFieldID }]` );
newFieldOptions = newFieldOptions.replace( regex.dataFieldID, `data-field-id="${ newFieldID }"` );
newFieldOptions = newFieldOptions.replace( regex.referenceID, `data-reference="${ newFieldID }"` );
newFieldOptions = newFieldOptions.replace( regex.elementID, regex.elementIdReplace );
// Hide all field options panels.
// Add a new field options panel.
$fieldOptions.after( `<div class="${ fieldOptionsClass }" id="wpforms-field-option-${ newFieldID }" data-field-id="${ newFieldID }">${ newFieldOptions }</div>` );
// Get a new field options panel.
const $newFieldOptions = $( `#wpforms-field-option-${ newFieldID }` );
// If the user duplicates an active field.
if ( $fieldActive.data( 'field-id' ) === id && $visibleTab.length ) {
// The following will help identify which tab from the sidebar panel settings is currently being viewed,
// i.e., "General," "Advanced," "Smart Logic," etc.
const visibleTabClassName = $visibleTab.attr( 'class' ).match( /wpforms-field-option-group-\S*/i )[ 0 ];
const $newFieldOptionsTab = $newFieldOptions.find( `>.${ visibleTabClassName }` );
// Remove any left-over state from previously duplicated options.
$newFieldOptions.find( '>' ).removeClass( 'active' );
// Set active tab to the same tab that was active before the duplication.
$newFieldOptionsTab.addClass( 'active' );
// If the user duplicates an inactive field.
if ( $fieldActive.data( 'field-id' ) !== id && $visibleTab.length ) {
// Remove active class from the current active tab.
$newFieldOptions.find( '>' ).removeClass( 'active' );
// Set active tab to "General".
$newFieldOptions.find( '>.wpforms-field-option-group-basic' ).addClass( 'active' );
$fieldOptions.find( ':input' ).each( function( index, el ) { // eslint-disable-line complexity, no-unused-vars
name = $this.attr( 'name' );
const newName = name.replace( regex.fieldID, `fields[${ newFieldID }]` ),
type = $this.attr( 'type' );
if ( type === 'checkbox' || type === 'radio' ) {
if ( $this.is( ':checked' ) ) {
$newFieldOptions.find( `[name="${ newName }"]` )
.attr( 'checked', 'checked' );
$newFieldOptions.find( `[name="${ newName }"]` )
.prop( 'checked', false )
.attr( 'checked', false );
if ( $this.is( 'select' ) ) {
if ( $this.find( 'option:selected' ).length ) {
const optionVal = $this.find( 'option:selected' ).val();
$newFieldOptions.find( `[name="${ newName }"]` )
.find( `[value="${ optionVal }"]` )
.prop( 'selected', true );
const value = $this.val();
if ( value === '' && $this.hasClass( 'wpforms-money-input' ) ) {
$newFieldOptions.find( `[name="${ newName }"]` ).val(
wpf.numberFormat( '0', wpforms_builder.currency_decimals, wpforms_builder.currency_decimal, wpforms_builder.currency_thousands )
// We've removed the empty value check here.
// If we are duplicating a field with no value, we should respect that.
$newFieldOptions.find( `[name="${ newName }"]` ).val( value );
$newFieldOptions.find( '.wpforms-field-option-hidden-id' ).val( newFieldID );
elements.$nextFieldId.val( nextID );
const $newFieldLabel = type === 'html' ? $( `#wpforms-field-option-${ newFieldID }-name` ) : $( `#wpforms-field-option-${ newFieldID }-label` );
// Adjust the label to indicate this is a copy.
$newFieldLabel.val( newFieldLabel ).trigger( 'input' );
// Fire field adds custom event.
$builder.trigger( 'wpformsFieldAdd', [ newFieldID, type ] );
// Re-init tooltips for a new field options panel.
// Re-init Modern Dropdown.
if ( isModernDropdown ) {
app.dropdownField.helpers.convertClassicToModern( id );
app.dropdownField.helpers.convertClassicToModern( newFieldID );
// Re-init instance in choices related fields.
app.fieldChoiceUpdate( $newField.data( 'field-type' ), newFieldID );
// Re-init color pickers.
* Add the entry preview field before the page break field.
* @param {Object} options Field options.
* @param {number} position The field position.
addEntryPreviewAndPageBreakFields( options, position ) {
app.fieldAdd( 'entry-preview', options ).done( function( res ) {
const entryPreviewId = res.data.field.id;
app.fieldAdd( 'pagebreak', { position: position + 1 } ).done( function( res ) {
app.lockEntryPreviewFieldsPosition( entryPreviewId );
const $pageBreakField = $( '#wpforms-field-' + res.data.field.id ),
$nextField = $pageBreakField.nextAll( '.wpforms-field-pagebreak, .wpforms-field-entry-preview' ).first();
if ( $nextField.hasClass( 'wpforms-field-entry-preview' ) ) {
app.lockEntryPreviewFieldsPosition( $nextField.data( 'field-id' ) );
* Stick an entry preview field after adding.
lockEntryPreviewFieldsPosition( id ) {
const $entryPreviewField = $( '#wpforms-field-' + id ),
$pageBreakField = $entryPreviewField.prevAll( '.wpforms-field-pagebreak:not(.wpforms-pagebreak-bottom)' ).first(),
$nextPageBreakField = $entryPreviewField.nextAll( '.wpforms-field-pagebreak' ).first(),
nextPageBreakFieldId = $nextPageBreakField.data( 'field-id' ),
$pageBreakOptions = $( '#wpforms-field-option-' + nextPageBreakFieldId ),
$pageBreakPrevToggle = $pageBreakOptions.find( '.wpforms-field-option-row-prev_toggle' ),
$pageBreakPrevToggleField = $pageBreakPrevToggle.find( 'input' );
$entryPreviewField.addClass( 'wpforms-field-not-draggable' );
$pageBreakField.addClass( 'wpforms-field-not-draggable wpforms-field-entry-preview-not-deleted' );
$pageBreakPrevToggleField.prop( 'checked', 'checked' ).trigger( 'change' );
$pageBreakPrevToggle.addClass( 'wpforms-entry-preview-block' );
$( '#wpforms-add-fields-entry-preview' ).removeClass( 'wpforms-entry-preview-adding' );
$builder.trigger( 'wpformsFieldDragToggle', [ id, $entryPreviewField.data( 'field-type' ) ] );
$builder.trigger( 'wpformsFieldDragToggle', [ $pageBreakField.data( 'field-id' ), $pageBreakField.data( 'field-type' ) ] );
* An entry preview field requires a page break that locates before.
* @param {jQuery} $fields List of fields in the form preview.
* @param {number} position The field position.
* @return {boolean} True if we need to add a page break field before.
isEntryPreviewFieldRequiresPageBreakBefore( $fields, position ) {
const $beforeFields = $fields.slice( 0, position ).filter( '.wpforms-field-pagebreak,.wpforms-field-entry-preview' );
let needPageBreakBefore = true;
if ( ! $beforeFields.length ) {
return needPageBreakBefore;
$( $beforeFields.get().reverse() ).each( function() {
if ( $this.hasClass( 'wpforms-field-entry-preview' ) ) {
if ( $this.hasClass( 'wpforms-field-pagebreak' ) && ! $this.hasClass( 'wpforms-field-stick' ) ) {
needPageBreakBefore = false;
return needPageBreakBefore;
* An entry preview field requires a page break that locates after.
* @param {jQuery} $fields List of fields in the form preview.
* @param {number} position The field position.
* @return {boolean} True if we need to add a page break field after.
isEntryPreviewFieldRequiresPageBreakAfter( $fields, position ) {
const $afterFields = $fields.slice( position ).filter( '.wpforms-field-pagebreak,.wpforms-field-entry-preview' );
let needPageBreakAfter = Boolean( $afterFields.length );
if ( ! $afterFields.length ) {
return needPageBreakAfter;
$afterFields.each( function() {
if ( $this.hasClass( 'wpforms-field-entry-preview' ) ) {
if ( $this.hasClass( 'wpforms-field-pagebreak' ) ) {
needPageBreakAfter = false;
return needPageBreakAfter;
* @since 1.6.4 Added hCaptcha support.
* @param {string} type Field type.
* @param {Object} options Additional options.
* @return {Promise|void} jQuery.post() promise interface.
fieldAdd( type, options ) { // eslint-disable-line max-lines-per-function
const $btn = $( `#wpforms-add-fields-${ type }` );
if ( $btn.hasClass( 'upgrade-modal' ) || $btn.hasClass( 'education-modal' ) || $btn.hasClass( 'warning-modal' ) ) {
if ( [ 'captcha_turnstile', 'captcha_hcaptcha', 'captcha_recaptcha', 'captcha_none' ].includes( type ) ) {
WPForms.Admin.Builder.DragFields.disableDragAndDrop();
app.disableFormActions();
if ( app.isUncheckedEntryPreviewField( type, options ) ) {
app.addEntryPreviewField( type, options );