: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
/***********************************************************************
* Begin public API methods
**********************************************************************/
* Enable/disable the reordering UI
* @param {boolean} showOrHide to enable/disable reordering
* @todo We should have a reordering state instead and rename this to onChangeReordering
toggleReordering: function( showOrHide ) {
var addNewWidgetBtn = this.$sectionContent.find( '.add-new-widget' ),
reorderBtn = this.container.find( '.reorder-toggle' ),
widgetsTitle = this.$sectionContent.find( '.widget-title' );
showOrHide = Boolean( showOrHide );
if ( showOrHide === this.$sectionContent.hasClass( 'reordering' ) ) {
this.isReordering = showOrHide;
this.$sectionContent.toggleClass( 'reordering', showOrHide );
_( this.getWidgetFormControls() ).each( function( formControl ) {
addNewWidgetBtn.attr({ 'tabindex': '-1', 'aria-hidden': 'true' });
reorderBtn.attr( 'aria-label', l10n.reorderLabelOff );
wp.a11y.speak( l10n.reorderModeOn );
// Hide widget titles while reordering: title is already in the reorder controls.
widgetsTitle.attr( 'aria-hidden', 'true' );
addNewWidgetBtn.removeAttr( 'tabindex aria-hidden' );
reorderBtn.attr( 'aria-label', l10n.reorderLabelOn );
wp.a11y.speak( l10n.reorderModeOff );
widgetsTitle.attr( 'aria-hidden', 'false' );
* Get the widget_form Customize controls associated with the current sidebar.
* @return {wp.customize.controlConstructor.widget_form[]}
getWidgetFormControls: function() {
_( this.setting() ).each( function( widgetId ) {
var settingId = widgetIdToSettingId( widgetId ),
formControl = api.control( settingId );
formControls.push( formControl );
* @param {string} widgetId or an id_base for adding a previously non-existing widget.
* @return {Object|false} widget_form control instance, or false on error.
addWidget: function( widgetId ) {
var self = this, controlHtml, $widget, controlType = 'widget_form', controlContainer, controlConstructor,
parsedWidgetId = parseWidgetId( widgetId ),
widgetNumber = parsedWidgetId.number,
widgetIdBase = parsedWidgetId.id_base,
widget = api.Widgets.availableWidgets.findWhere( {id_base: widgetIdBase} ),
settingId, isExistingWidget, widgetFormControl, sidebarWidgets, settingArgs, setting;
if ( widgetNumber && ! widget.get( 'is_multi' ) ) {
// Set up new multi widget.
if ( widget.get( 'is_multi' ) && ! widgetNumber ) {
widget.set( 'multi_number', widget.get( 'multi_number' ) + 1 );
widgetNumber = widget.get( 'multi_number' );
controlHtml = $( '#widget-tpl-' + widget.get( 'id' ) ).html().trim();
if ( widget.get( 'is_multi' ) ) {
controlHtml = controlHtml.replace( /<[^<>]+>/g, function( m ) {
return m.replace( /__i__|%i%/g, widgetNumber );
widget.set( 'is_disabled', true ); // Prevent single widget from being added again now.
$widget = $( controlHtml );
controlContainer = $( '<li/>' )
.addClass( 'customize-control' )
.addClass( 'customize-control-' + controlType )
// Remove icon which is visible inside the panel.
controlContainer.find( '> .widget-icon' ).remove();
if ( widget.get( 'is_multi' ) ) {
controlContainer.find( 'input[name="widget_number"]' ).val( widgetNumber );
controlContainer.find( 'input[name="multi_number"]' ).val( widgetNumber );
widgetId = controlContainer.find( '[name="widget-id"]' ).val();
controlContainer.hide(); // To be slid-down below.
settingId = 'widget_' + widget.get( 'id_base' );
if ( widget.get( 'is_multi' ) ) {
settingId += '[' + widgetNumber + ']';
controlContainer.attr( 'id', 'customize-control-' + settingId.replace( /\]/g, '' ).replace( /\[/g, '-' ) );
// Only create setting if it doesn't already exist (if we're adding a pre-existing inactive widget).
isExistingWidget = api.has( settingId );
if ( ! isExistingWidget ) {
transport: api.Widgets.data.selectiveRefreshableWidgets[ widget.get( 'id_base' ) ] ? 'postMessage' : 'refresh',
previewer: this.setting.previewer
setting = api.create( settingId, settingId, '', settingArgs );
setting.set( {} ); // Mark dirty, changing from '' to {}.
controlConstructor = api.controlConstructor[controlType];
widgetFormControl = new controlConstructor( settingId, {
content: controlContainer,
sidebar_id: self.params.sidebar_id,
widget_id_base: widget.get( 'id_base' ),
is_new: ! isExistingWidget,
width: widget.get( 'width' ),
height: widget.get( 'height' ),
is_wide: widget.get( 'is_wide' )
api.control.add( widgetFormControl );
// Make sure widget is removed from the other sidebars.
api.each( function( otherSetting ) {
if ( otherSetting.id === self.setting.id ) {
if ( 0 !== otherSetting.id.indexOf( 'sidebars_widgets[' ) ) {
var otherSidebarWidgets = otherSetting().slice(),
i = _.indexOf( otherSidebarWidgets, widgetId );
otherSidebarWidgets.splice( i );
otherSetting( otherSidebarWidgets );
// Add widget to this sidebar.
sidebarWidgets = this.setting().slice();
if ( -1 === _.indexOf( sidebarWidgets, widgetId ) ) {
sidebarWidgets.push( widgetId );
this.setting( sidebarWidgets );
controlContainer.slideDown( function() {
if ( isExistingWidget ) {
widgetFormControl.updateWidget( {
instance: widgetFormControl.setting()
return widgetFormControl;
// Register models for custom panel, section, and control types.
$.extend( api.panelConstructor, {
widgets: api.Widgets.WidgetsPanel
$.extend( api.sectionConstructor, {
sidebar: api.Widgets.SidebarSection
$.extend( api.controlConstructor, {
widget_form: api.Widgets.WidgetControl,
sidebar_widgets: api.Widgets.SidebarControl
* Init Customizer for widgets.
api.bind( 'ready', function() {
// Set up the widgets panel.
api.Widgets.availableWidgetsPanel = new api.Widgets.AvailableWidgetsPanelView({
collection: api.Widgets.availableWidgets
// Highlight widget control.
api.previewer.bind( 'highlight-widget-control', api.Widgets.highlightWidgetFormControl );
// Open and focus widget control.
api.previewer.bind( 'focus-widget-control', api.Widgets.focusWidgetFormControl );
* Highlight a widget control.
* @param {string} widgetId
api.Widgets.highlightWidgetFormControl = function( widgetId ) {
var control = api.Widgets.getWidgetFormControlForWidget( widgetId );
control.highlightSectionAndControl();
* Focus a widget control.
* @param {string} widgetId
api.Widgets.focusWidgetFormControl = function( widgetId ) {
var control = api.Widgets.getWidgetFormControlForWidget( widgetId );
* Given a widget control, find the sidebar widgets control that contains it.
* @param {string} widgetId
api.Widgets.getSidebarWidgetControlContainingWidget = function( widgetId ) {
// @todo This can use widgetIdToSettingId(), then pass into wp.customize.control( x ).getSidebarWidgetsControl().
api.control.each( function( control ) {
if ( control.params.type === 'sidebar_widgets' && -1 !== _.indexOf( control.setting(), widgetId ) ) {
* Given a widget ID for a widget appearing in the preview, get the widget form control associated with it.
* @param {string} widgetId
api.Widgets.getWidgetFormControlForWidget = function( widgetId ) {
// @todo We can just use widgetIdToSettingId() here.
api.control.each( function( control ) {
if ( control.params.type === 'widget_form' && control.params.widget_id === widgetId ) {
* Initialize Edit Menu button in Nav Menu widget.
$( document ).on( 'widget-added', function( event, widgetContainer ) {
var parsedWidgetId, widgetControl, navMenuSelect, editMenuButton;
parsedWidgetId = parseWidgetId( widgetContainer.find( '> .widget-inside > .form > .widget-id' ).val() );
if ( 'nav_menu' !== parsedWidgetId.id_base ) {
widgetControl = api.control( 'widget_nav_menu[' + String( parsedWidgetId.number ) + ']' );
navMenuSelect = widgetContainer.find( 'select[name*="nav_menu"]' );
editMenuButton = widgetContainer.find( '.edit-selected-nav-menu > button' );
if ( 0 === navMenuSelect.length || 0 === editMenuButton.length ) {
navMenuSelect.on( 'change', function() {
if ( api.section.has( 'nav_menu[' + navMenuSelect.val() + ']' ) ) {
editMenuButton.parent().show();
editMenuButton.parent().hide();
editMenuButton.on( 'click', function() {
var section = api.section( 'nav_menu[' + navMenuSelect.val() + ']' );
focusConstructWithBreadcrumb( section, widgetControl );
* Focus (expand) one construct and then focus on another construct after the first is collapsed.
* This overrides the back button to serve the purpose of breadcrumb navigation.
* @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} focusConstruct - The object to initially focus.
* @param {wp.customize.Section|wp.customize.Panel|wp.customize.Control} returnConstruct - The object to return focus.
function focusConstructWithBreadcrumb( focusConstruct, returnConstruct ) {
function onceCollapsed( isExpanded ) {
focusConstruct.expanded.unbind( onceCollapsed );
focusConstruct.expanded.bind( onceCollapsed );
* @param {string} widgetId
function parseWidgetId( widgetId ) {
matches = widgetId.match( /^(.+)-(\d+)$/ );
parsed.id_base = matches[1];
parsed.number = parseInt( matches[2], 10 );
// Likely an old single widget.
parsed.id_base = widgetId;
* @param {string} widgetId
* @return {string} settingId
function widgetIdToSettingId( widgetId ) {
var parsed = parseWidgetId( widgetId ), settingId;
settingId = 'widget_' + parsed.id_base;
settingId += '[' + parsed.number + ']';