: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
getWidgetSidebarPosition: function() {
var sidebarWidgetIds, position;
sidebarWidgetIds = this.getSidebarWidgetsControl().setting();
position = _.indexOf( sidebarWidgetIds, this.params.widget_id );
* Move widget up one in the sidebar
this._moveWidgetByOne( -1 );
* Move widget up one in the sidebar
this._moveWidgetByOne( 1 );
* @param {number} offset 1|-1
_moveWidgetByOne: function( offset ) {
var i, sidebarWidgetsSetting, sidebarWidgetIds, adjacentWidgetId;
i = this.getWidgetSidebarPosition();
sidebarWidgetsSetting = this.getSidebarWidgetsControl().setting;
sidebarWidgetIds = Array.prototype.slice.call( sidebarWidgetsSetting() ); // Clone.
adjacentWidgetId = sidebarWidgetIds[i + offset];
sidebarWidgetIds[i + offset] = this.params.widget_id;
sidebarWidgetIds[i] = adjacentWidgetId;
sidebarWidgetsSetting( sidebarWidgetIds );
* Toggle visibility of the widget move area
* @param {boolean} [showOrHide]
toggleWidgetMoveArea: function( showOrHide ) {
var self = this, $moveWidgetArea;
$moveWidgetArea = this.container.find( '.move-widget-area' );
if ( typeof showOrHide === 'undefined' ) {
showOrHide = ! $moveWidgetArea.hasClass( 'active' );
// Reset the selected sidebar.
$moveWidgetArea.find( '.selected' ).removeClass( 'selected' );
$moveWidgetArea.find( 'li' ).filter( function() {
return $( this ).data( 'id' ) === self.params.sidebar_id;
} ).addClass( 'selected' );
this.container.find( '.move-widget-btn' ).prop( 'disabled', true );
$moveWidgetArea.toggleClass( 'active', showOrHide );
* Highlight the widget control and section
highlightSectionAndControl: function() {
if ( this.container.is( ':hidden' ) ) {
$target = this.container.closest( '.control-section' );
$target = this.container;
$( '.highlighted' ).removeClass( 'highlighted' );
$target.addClass( 'highlighted' );
$target.removeClass( 'highlighted' );
* wp.customize.Widgets.WidgetsPanel
* Customizer panel containing the widget area sections.
* @class wp.customize.Widgets.WidgetsPanel
* @augments wp.customize.Panel
api.Widgets.WidgetsPanel = api.Panel.extend(/** @lends wp.customize.Widgets.WigetsPanel.prototype */{
* Add and manage the display of the no-rendered-areas notice.
api.Panel.prototype.ready.call( panel );
panel.deferred.embedded.done(function() {
var panelMetaContainer, noticeContainer, updateNotice, getActiveSectionCount, shouldShowNotice;
panelMetaContainer = panel.container.find( '.panel-meta' );
// @todo This should use the Notifications API introduced to panels. See <https://core.trac.wordpress.org/ticket/38794>.
noticeContainer = $( '<div></div>', {
'class': 'no-widget-areas-rendered-notice',
panelMetaContainer.append( noticeContainer );
* Get the number of active sections in the panel.
* @return {number} Number of active sidebar sections.
getActiveSectionCount = function() {
return _.filter( panel.sections(), function( section ) {
return 'sidebar' === section.params.type && section.active();
* Determine whether or not the notice should be displayed.
shouldShowNotice = function() {
var activeSectionCount = getActiveSectionCount();
if ( 0 === activeSectionCount ) {
return activeSectionCount !== api.Widgets.data.registeredSidebars.length;
updateNotice = function() {
var activeSectionCount = getActiveSectionCount(), someRenderedMessage, nonRenderedAreaCount, registeredAreaCount;
registeredAreaCount = api.Widgets.data.registeredSidebars.length;
if ( activeSectionCount !== registeredAreaCount ) {
if ( 0 !== activeSectionCount ) {
nonRenderedAreaCount = registeredAreaCount - activeSectionCount;
someRenderedMessage = l10n.someAreasShown[ nonRenderedAreaCount ];
someRenderedMessage = l10n.noAreasShown;
if ( someRenderedMessage ) {
noticeContainer.append( $( '<p></p>', {
text: someRenderedMessage
noticeContainer.append( $( '<p></p>', {
text: l10n.navigatePreview
* Set the initial visibility state for rendered notice.
* Update the visibility of the notice whenever a reflow happens.
noticeContainer.toggle( shouldShowNotice() );
api.previewer.deferred.active.done( function () {
noticeContainer.toggle( shouldShowNotice() );
api.bind( 'pane-contents-reflowed', function() {
var duration = ( 'resolved' === api.previewer.deferred.active.state() ) ? 'fast' : 0;
if ( shouldShowNotice() ) {
noticeContainer.slideDown( duration );
noticeContainer.slideUp( duration );
* Allow an active widgets panel to be contextually active even when it has no active sections (widget areas).
* This ensures that the widgets panel appears even when there are no
* sidebars displayed on the URL currently being previewed.
isContextuallyActive: function() {
* wp.customize.Widgets.SidebarSection
* Customizer section representing a widget area widget
* @class wp.customize.Widgets.SidebarSection
* @augments wp.customize.Section
api.Widgets.SidebarSection = api.Section.extend(/** @lends wp.customize.Widgets.SidebarSection.prototype */{
* Sync the section's active state back to the Backbone model's is_rendered attribute
var section = this, registeredSidebar;
api.Section.prototype.ready.call( this );
registeredSidebar = api.Widgets.registeredSidebars.get( section.params.sidebarId );
section.active.bind( function ( active ) {
registeredSidebar.set( 'is_rendered', active );
registeredSidebar.set( 'is_rendered', section.active() );
* wp.customize.Widgets.SidebarControl
* Customizer control for widgets.
* Note that 'sidebar_widgets' must match the WP_Widget_Area_Customize_Control::$type
* @class wp.customize.Widgets.SidebarControl
* @augments wp.customize.Control
api.Widgets.SidebarControl = api.Control.extend(/** @lends wp.customize.Widgets.SidebarControl.prototype */{
this.$controlSection = this.container.closest( '.control-section' );
this.$sectionContent = this.container.closest( '.accordion-section-content' );
this._applyCardinalOrderClassNames();
* Update ordering of widget control forms when the setting is updated
_setupModel: function() {
this.setting.bind( function( newWidgetIds, oldWidgetIds ) {
var widgetFormControls, removedWidgetIds, priority;
removedWidgetIds = _( oldWidgetIds ).difference( newWidgetIds );
// Filter out any persistent widget IDs for widgets which have been deactivated.
newWidgetIds = _( newWidgetIds ).filter( function( newWidgetId ) {
var parsedWidgetId = parseWidgetId( newWidgetId );
return !! api.Widgets.availableWidgets.findWhere( { id_base: parsedWidgetId.id_base } );
widgetFormControls = _( newWidgetIds ).map( function( widgetId ) {
var widgetFormControl = api.Widgets.getWidgetFormControlForWidget( widgetId );
if ( ! widgetFormControl ) {
widgetFormControl = self.addWidget( widgetId );
return widgetFormControl;
// Sort widget controls to their new positions.
widgetFormControls.sort( function( a, b ) {
var aIndex = _.indexOf( newWidgetIds, a.params.widget_id ),
bIndex = _.indexOf( newWidgetIds, b.params.widget_id );
_( widgetFormControls ).each( function ( control ) {
control.priority( priority );
control.section( self.section() );
self.priority( priority ); // Make sure sidebar control remains at end.
// Re-sort widget form controls (including widgets form other sidebars newly moved here).
self._applyCardinalOrderClassNames();
// If the widget was dragged into the sidebar, make sure the sidebar_id param is updated.
_( widgetFormControls ).each( function( widgetFormControl ) {
widgetFormControl.params.sidebar_id = self.params.sidebar_id;
// Cleanup after widget removal.
_( removedWidgetIds ).each( function( removedWidgetId ) {
// Using setTimeout so that when moving a widget to another sidebar,
// the other sidebars_widgets settings get a chance to update.
var removedControl, wasDraggedToAnotherSidebar, inactiveWidgets, removedIdBase,
widget, isPresentInAnotherSidebar = false;
// Check if the widget is in another sidebar.
api.each( function( otherSetting ) {
if ( otherSetting.id === self.setting.id || 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) || otherSetting.id === 'sidebars_widgets[wp_inactive_widgets]' ) {
var otherSidebarWidgets = otherSetting(), i;
i = _.indexOf( otherSidebarWidgets, removedWidgetId );
isPresentInAnotherSidebar = true;
// If the widget is present in another sidebar, abort!
if ( isPresentInAnotherSidebar ) {
removedControl = api.Widgets.getWidgetFormControlForWidget( removedWidgetId );
// Detect if widget control was dragged to another sidebar.
wasDraggedToAnotherSidebar = removedControl && $.contains( document, removedControl.container[0] ) && ! $.contains( self.$sectionContent[0], removedControl.container[0] );
// Delete any widget form controls for removed widgets.
if ( removedControl && ! wasDraggedToAnotherSidebar ) {
api.control.remove( removedControl.id );
removedControl.container.remove();
// Move widget to inactive widgets sidebar (move it to Trash) if has been previously saved.
// This prevents the inactive widgets sidebar from overflowing with throwaway widgets.
if ( api.Widgets.savedWidgetIds[removedWidgetId] ) {
inactiveWidgets = api.value( 'sidebars_widgets[wp_inactive_widgets]' )().slice();
inactiveWidgets.push( removedWidgetId );
api.value( 'sidebars_widgets[wp_inactive_widgets]' )( _( inactiveWidgets ).unique() );
// Make old single widget available for adding again.
removedIdBase = parseWidgetId( removedWidgetId ).id_base;
widget = api.Widgets.availableWidgets.findWhere( { id_base: removedIdBase } );
if ( widget && ! widget.get( 'is_multi' ) ) {
widget.set( 'is_disabled', false );
* Allow widgets in sidebar to be re-ordered, and for the order to be previewed
_setupSortable: function() {
this.isReordering = false;
* Update widget order setting when controls are re-ordered
this.$sectionContent.sortable( {
items: '> .customize-control-widget_form',
connectWith: '.accordion-section-content:has(.customize-control-sidebar_widgets)',
var widgetContainerIds = self.$sectionContent.sortable( 'toArray' ), widgetIds;
widgetIds = $.map( widgetContainerIds, function( widgetContainerId ) {
return $( '#' + widgetContainerId ).find( ':input[name=widget-id]' ).val();
self.setting( widgetIds );
* Expand other Customizer sidebar section when dragging a control widget over it,
* allowing the control to be dropped into another section
this.$controlSection.find( '.accordion-section-title' ).droppable({
accept: '.customize-control-widget_form',
var section = api.section( self.section.get() );
allowMultiple: true, // Prevent the section being dragged from to be collapsed.
completeCallback: function () {
// @todo It is not clear when refreshPositions should be called on which sections, or if it is even needed.
api.section.each( function ( otherSection ) {
if ( otherSection.container.find( '.customize-control-sidebar_widgets' ).length ) {
otherSection.container.find( '.accordion-section-content:first' ).sortable( 'refreshPositions' );
* Keyboard-accessible reordering
this.container.find( '.reorder-toggle' ).on( 'click', function() {
self.toggleReordering( ! self.isReordering );
* Set up UI for adding a new widget
_setupAddition: function() {
this.container.find( '.add-new-widget' ).on( 'click', function() {
var addNewWidgetBtn = $( this );
if ( self.$sectionContent.hasClass( 'reordering' ) ) {
if ( ! $( 'body' ).hasClass( 'adding-widget' ) ) {
addNewWidgetBtn.attr( 'aria-expanded', 'true' );
api.Widgets.availableWidgetsPanel.open( self );
addNewWidgetBtn.attr( 'aria-expanded', 'false' );
api.Widgets.availableWidgetsPanel.close();
* Add classes to the widget_form controls to assist with styling
_applyCardinalOrderClassNames: function() {
_.each( this.setting(), function ( widgetId ) {
var widgetControl = api.Widgets.getWidgetFormControlForWidget( widgetId );
widgetControls.push( widgetControl );
if ( 0 === widgetControls.length || ( 1 === api.Widgets.registeredSidebars.length && widgetControls.length <= 1 ) ) {
this.container.find( '.reorder-toggle' ).hide();
this.container.find( '.reorder-toggle' ).show();
$( widgetControls ).each( function () {
.removeClass( 'first-widget' )
.removeClass( 'last-widget' )
.find( '.move-widget-down, .move-widget-up' ).prop( 'tabIndex', 0 );
_.first( widgetControls ).container
.addClass( 'first-widget' )
.find( '.move-widget-up' ).prop( 'tabIndex', -1 );
_.last( widgetControls ).container
.addClass( 'last-widget' )
.find( '.move-widget-down' ).prop( 'tabIndex', -1 );