: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
self.container.slideUp( function() {
var sidebarsWidgetsControl = api.Widgets.getSidebarWidgetControlContainingWidget( self.params.widget_id ),
if ( ! sidebarsWidgetsControl ) {
sidebarWidgetIds = sidebarsWidgetsControl.setting().slice();
i = _.indexOf( sidebarWidgetIds, self.params.widget_id );
sidebarWidgetIds.splice( i, 1 );
sidebarsWidgetsControl.setting( sidebarWidgetIds );
$adjacentFocusTarget.focus(); // Keyboard accessibility.
replaceDeleteWithRemove = function() {
$removeBtn.text( l10n.removeBtnLabel ); // wp_widget_control() outputs the button as "Delete".
$removeBtn.attr( 'title', l10n.removeBtnTooltip );
if ( this.params.is_new ) {
api.bind( 'saved', replaceDeleteWithRemove );
replaceDeleteWithRemove();
* Find all inputs in a widget container that should be considered when
* comparing the loaded form with the sanitized form, whose fields will
* be aligned to copy the sanitized over. The elements returned by this
* are passed into this._getInputsSignature(), and they are iterated
* over when copying sanitized values over to the form loaded.
* @param {jQuery} container element in which to look for inputs
* @return {jQuery} inputs
_getInputs: function( container ) {
return $( container ).find( ':input[name]' );
* Iterate over supplied inputs and create a signature string for all of them together.
* This string can be used to compare whether or not the form has all of the same fields.
_getInputsSignature: function( inputs ) {
var inputsSignatures = _( inputs ).map( function( input ) {
var $input = $( input ), signatureParts;
if ( $input.is( ':checkbox, :radio' ) ) {
signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ), $input.prop( 'value' ) ];
signatureParts = [ $input.attr( 'id' ), $input.attr( 'name' ) ];
return signatureParts.join( ',' );
return inputsSignatures.join( ';' );
* Get the state for an input depending on its type.
* @param {jQuery|Element} input
* @return {string|boolean|Array|*}
_getInputState: function( input ) {
if ( input.is( ':radio, :checkbox' ) ) {
return input.prop( 'checked' );
} else if ( input.is( 'select[multiple]' ) ) {
return input.find( 'option:selected' ).map( function () {
* Update an input's state based on its type.
* @param {jQuery|Element} input
* @param {string|boolean|Array|*} state
_setInputState: function ( input, state ) {
if ( input.is( ':radio, :checkbox' ) ) {
input.prop( 'checked', state );
} else if ( input.is( 'select[multiple]' ) ) {
if ( ! Array.isArray( state ) ) {
// Make sure all state items are strings since the DOM value is a string.
state = _.map( state, function ( value ) {
input.find( 'option' ).each( function () {
$( this ).prop( 'selected', -1 !== _.indexOf( state, String( this.value ) ) );
/***********************************************************************
* Begin public API methods
**********************************************************************/
* @return {wp.customize.controlConstructor.sidebar_widgets[]}
getSidebarWidgetsControl: function() {
var settingId, sidebarWidgetsControl;
settingId = 'sidebars_widgets[' + this.params.sidebar_id + ']';
sidebarWidgetsControl = api.control( settingId );
if ( ! sidebarWidgetsControl ) {
return sidebarWidgetsControl;
* Submit the widget form via Ajax and get back the updated instance,
* along with the new widget control form to render.
* @param {Object|null} [args.instance=null] When the model changes, the instance is sent here; otherwise, the inputs from the form are used
* @param {Function|null} [args.complete=null] Function which is called when the request finishes. Context is bound to the control. First argument is any error. Following arguments are for success.
* @param {boolean} [args.ignoreActiveElement=false] Whether or not updating a field will be deferred if focus is still on the element.
updateWidget: function( args ) {
var self = this, instanceOverride, completeCallback, $widgetRoot, $widgetContent,
updateNumber, params, data, $inputs, processing, jqxhr, isChanged;
// The updateWidget logic requires that the form fields to be fully present.
self.embedWidgetContent();
ignoreActiveElement: false
instanceOverride = args.instance;
completeCallback = args.complete;
updateNumber = this._updateCount;
$widgetRoot = this.container.find( '.widget:first' );
$widgetContent = $widgetRoot.find( '.widget-content:first' );
// Remove a previous error message.
$widgetContent.find( '.widget-error' ).remove();
this.container.addClass( 'widget-form-loading' );
this.container.addClass( 'previewer-loading' );
processing = api.state( 'processing' );
processing( processing() + 1 );
if ( ! this.liveUpdateMode ) {
this.container.addClass( 'widget-form-disabled' );
params.action = 'update-widget';
params.wp_customize = 'on';
params.nonce = api.settings.nonce['update-widget'];
params.customize_theme = api.settings.theme.stylesheet;
params.customized = wp.customize.previewer.query().customized;
data = $.param( params );
$inputs = this._getInputs( $widgetContent );
* Store the value we're submitting in data so that when the response comes back,
* we know if it got sanitized; if there is no difference in the sanitized value,
* then we do not need to touch the UI and mess up the user's ongoing editing.
$inputs.each( function() {
$( this ).data( 'state' + updateNumber, self._getInputState( this ) );
if ( instanceOverride ) {
data += '&' + $.param( { 'sanitized_widget_setting': JSON.stringify( instanceOverride ) } );
data += '&' + $inputs.serialize();
data += '&' + $widgetContent.find( '~ :input' ).serialize();
if ( this._previousUpdateRequest ) {
this._previousUpdateRequest.abort();
jqxhr = $.post( wp.ajax.settings.url, data );
this._previousUpdateRequest = jqxhr;
jqxhr.done( function( r ) {
var message, sanitizedForm, $sanitizedInputs, hasSameInputsInResponse,
isLiveUpdateAborted = false;
// Check if the user is logged out.
api.previewer.preview.iframe.hide();
api.previewer.login().done( function() {
self.updateWidget( args );
api.previewer.preview.iframe.show();
sanitizedForm = $( '<div>' + r.data.form + '</div>' );
$sanitizedInputs = self._getInputs( sanitizedForm );
hasSameInputsInResponse = self._getInputsSignature( $inputs ) === self._getInputsSignature( $sanitizedInputs );
// Restore live update mode if sanitized fields are now aligned with the existing fields.
if ( hasSameInputsInResponse && ! self.liveUpdateMode ) {
self.liveUpdateMode = true;
self.container.removeClass( 'widget-form-disabled' );
self.container.find( 'input[name="savewidget"]' ).hide();
// Sync sanitized field states to existing fields if they are aligned.
if ( hasSameInputsInResponse && self.liveUpdateMode ) {
$inputs.each( function( i ) {
$sanitizedInput = $( $sanitizedInputs[i] ),
submittedState, sanitizedState, canUpdateState;
submittedState = $input.data( 'state' + updateNumber );
sanitizedState = self._getInputState( $sanitizedInput );
$input.data( 'sanitized', sanitizedState );
canUpdateState = ( ! _.isEqual( submittedState, sanitizedState ) && ( args.ignoreActiveElement || ! $input.is( document.activeElement ) ) );
self._setInputState( $input, sanitizedState );
$( document ).trigger( 'widget-synced', [ $widgetRoot, r.data.form ] );
// Otherwise, if sanitized fields are not aligned with existing fields, disable live update mode if enabled.
} else if ( self.liveUpdateMode ) {
self.liveUpdateMode = false;
self.container.find( 'input[name="savewidget"]' ).show();
isLiveUpdateAborted = true;
// Otherwise, replace existing form with the sanitized form.
$widgetContent.html( r.data.form );
self.container.removeClass( 'widget-form-disabled' );
$( document ).trigger( 'widget-updated', [ $widgetRoot ] );
* If the old instance is identical to the new one, there is nothing new
* needing to be rendered, and so we can preempt the event for the
* preview finishing loading.
isChanged = ! isLiveUpdateAborted && ! _( self.setting() ).isEqual( r.data.instance );
self.isWidgetUpdating = true; // Suppress triggering another updateWidget.
self.setting( r.data.instance );
self.isWidgetUpdating = false;
// No change was made, so stop the spinner now instead of when the preview would updates.
self.container.removeClass( 'previewer-loading' );
if ( completeCallback ) {
completeCallback.call( self, null, { noChange: ! isChanged, ajaxFinished: true } );
// General error message.
if ( r.data && r.data.message ) {
message = r.data.message;
if ( completeCallback ) {
completeCallback.call( self, message );
$widgetContent.prepend( '<p class="widget-error"><strong>' + message + '</strong></p>' );
jqxhr.fail( function( jqXHR, textStatus ) {
if ( completeCallback ) {
completeCallback.call( self, textStatus );
jqxhr.always( function() {
self.container.removeClass( 'widget-form-loading' );
$inputs.each( function() {
$( this ).removeData( 'state' + updateNumber );
processing( processing() - 1 );
* Expand the accordion section containing a control
expandControlSection: function() {
api.Control.prototype.expand.call( this );
* @param {Boolean} expanded
* @param {Object} [params]
* @return {Boolean} False if state already applied.
_toggleExpanded: api.Section.prototype._toggleExpanded,
* @param {Object} [params]
* @return {Boolean} False if already expanded.
expand: api.Section.prototype.expand,
* Expand the widget form control
* @deprecated 4.1.0 Use this.expand() instead.
* @param {Object} [params]
* @return {Boolean} False if already collapsed.
collapse: api.Section.prototype.collapse,
* Collapse the widget form control
* @deprecated 4.1.0 Use this.collapse() instead.
collapseForm: function() {
* Expand or collapse the widget control
* @deprecated this is poor naming, and it is better to directly set control.expanded( showOrHide )
* @param {boolean|undefined} [showOrHide] If not supplied, will be inverse of current visibility
toggleForm: function( showOrHide ) {
if ( typeof showOrHide === 'undefined' ) {
showOrHide = ! this.expanded();
this.expanded( showOrHide );
* Respond to change in the expanded state.
* @param {boolean} expanded
* @param {Object} args merged on top of this.defaultActiveArguments
onChangeExpanded: function ( expanded, args ) {
var self = this, $widget, $inside, complete, prevComplete, expandControl, $toggleBtn;
self.embedWidgetControl(); // Make sure the outer form is embedded so that the expanded state can be set in the UI.
self.embedWidgetContent();
// If the expanded state is unchanged only manipulate container expanded states.
api.Control.prototype.expand.call( self, {
completeCallback: args.completeCallback
$widget = this.container.find( 'div.widget:first' );
$inside = $widget.find( '.widget-inside:first' );
$toggleBtn = this.container.find( '.widget-top button.widget-action' );
expandControl = function() {
// Close all other widget controls before expanding this one.
api.control.each( function( otherControl ) {
if ( self.params.type === otherControl.params.type && self !== otherControl ) {
self.container.removeClass( 'expanding' );
self.container.addClass( 'expanded' );
$widget.addClass( 'open' );
$toggleBtn.attr( 'aria-expanded', 'true' );
self.container.trigger( 'expanded' );
if ( args.completeCallback ) {
if ( self.params.is_wide ) {
$inside.fadeIn( args.duration, complete );
$inside.slideDown( args.duration, complete );
self.container.trigger( 'expand' );
self.container.addClass( 'expanding' );
if ( $toggleBtn.attr( 'aria-expanded' ) === 'false' ) {
if ( api.section.has( self.section() ) ) {
api.section( self.section() ).expand( {
completeCallback: expandControl
self.container.removeClass( 'collapsing' );
self.container.removeClass( 'expanded' );
$widget.removeClass( 'open' );
$toggleBtn.attr( 'aria-expanded', 'false' );
self.container.trigger( 'collapsed' );
if ( args.completeCallback ) {
self.container.trigger( 'collapse' );
self.container.addClass( 'collapsing' );
if ( self.params.is_wide ) {
$inside.fadeOut( args.duration, complete );
$inside.slideUp( args.duration, function() {
$widget.css( { width:'', margin:'' } );
* Get the position (index) of the widget in the containing sidebar