: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Add the control for managing the menu name.
menuNameControlId = section.id + '[name]';
menuNameControl = api.control( menuNameControlId );
if ( ! menuNameControl ) {
menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
label: api.Menus.data.l10n.menuNameLabel,
api.control.add( menuNameControl );
menuNameControl.active.set( true );
menuControl = api.control( section.id );
menuControl = new api.controlConstructor.nav_menu( section.id, {
menu_id: section.params.menu_id
api.control.add( menuControl );
menuControl.active.set( true );
// Add the menu locations control.
menuLocationsControlId = section.id + '[locations]';
menuLocationsControl = api.control( menuLocationsControlId );
if ( ! menuLocationsControl ) {
menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
menu_id: section.params.menu_id
api.control.add( menuLocationsControl.id, menuLocationsControl );
menuControl.active.set( true );
// Add the control for managing the menu auto_add.
menuAutoAddControlId = section.id + '[auto_add]';
menuAutoAddControl = api.control( menuAutoAddControlId );
if ( ! menuAutoAddControl ) {
menuAutoAddControl = new api.controlConstructor.nav_menu_auto_add( menuAutoAddControlId, {
type: 'nav_menu_auto_add',
api.control.add( menuAutoAddControl );
menuAutoAddControl.active.set( true );
// Add the control for deleting the menu.
menuDeleteControlId = section.id + '[delete]';
menuDeleteControl = api.control( menuDeleteControlId );
if ( ! menuDeleteControl ) {
menuDeleteControl = new api.Control( menuDeleteControlId, {
templateId: 'nav-menu-delete-button'
api.control.add( menuDeleteControl.id, menuDeleteControl );
menuDeleteControl.active.set( true );
menuDeleteControl.deferred.embedded.done( function () {
menuDeleteControl.container.find( 'button' ).on( 'click', function() {
var menuId = section.params.menu_id;
var menuControl = api.Menus.getMenuControl( menuId );
menuControl.setting.set( false );
refreshAssignedLocations: function() {
menuTermId = section.params.menu_id,
currentAssignedLocations = [];
_.each( section.navMenuLocationSettings, function( setting, themeLocation ) {
if ( setting() === menuTermId ) {
currentAssignedLocations.push( themeLocation );
section.assignedLocations.set( currentAssignedLocations );
* @param {Array} themeLocationSlugs Theme location slugs.
updateAssignedLocationsInSectionTitle: function( themeLocationSlugs ) {
$title = section.container.find( '.accordion-section-title:first' );
$title.find( '.menu-in-location' ).remove();
_.each( themeLocationSlugs, function( themeLocationSlug ) {
var $label, locationName;
$label = $( '<span class="menu-in-location"></span>' );
locationName = api.Menus.data.locationSlugMappedToName[ themeLocationSlug ];
$label.text( api.Menus.data.l10n.menuLocation.replace( '%s', locationName ) );
section.container.toggleClass( 'assigned-to-menu-location', 0 !== themeLocationSlugs.length );
onChangeExpanded: function( expanded, args ) {
var section = this, completeCallback;
wpNavMenu.menuList = section.contentContainer;
wpNavMenu.targetList = wpNavMenu.menuList;
// Add attributes needed by wpNavMenu.
$( '#menu-to-edit' ).removeAttr( 'id' );
wpNavMenu.menuList.attr( 'id', 'menu-to-edit' ).addClass( 'menu' );
api.Menus.MenuItemControl.prototype.initAccessibility();
_.each( api.section( section.id ).controls(), function( control ) {
if ( 'nav_menu_item' === control.params.type ) {
// Make sure Sortables is initialized after the section has been expanded to prevent `offset` issues.
if ( args.completeCallback ) {
completeCallback = args.completeCallback;
args.completeCallback = function() {
if ( 'resolved' !== section.deferred.initSortables.state() ) {
wpNavMenu.initSortables(); // Depends on menu-to-edit ID being set above.
section.deferred.initSortables.resolve( wpNavMenu.menuList ); // Now MenuControl can extend the sortable.
// @todo Note that wp.customize.reflowPaneContents() is debounced,
// so this immediate change will show a slight flicker while priorities get updated.
api.control( 'nav_menu[' + String( section.params.menu_id ) + ']' ).reflowMenuItems();
if ( _.isFunction( completeCallback ) ) {
api.Section.prototype.onChangeExpanded.call( section, expanded, args );
* Highlight how a user may create new menu items.
* This method reminds the user to create new menu items and how.
* It's exposed this way because this class knows best which UI needs
* highlighted but those expanding this section know more about why and
* when the affordance should be highlighted.
highlightNewItemButton: function() {
api.utils.highlightButton( this.contentContainer.find( '.add-new-menu-item' ), { delay: 2000 } );
* Create a nav menu setting and section.
* @param {string} [name=''] Nav menu name.
* @return {wp.customize.Menus.MenuSection} Added nav menu.
api.Menus.createNavMenu = function createNavMenu( name ) {
var customizeId, placeholderId, setting;
placeholderId = api.Menus.generatePlaceholderAutoIncrementId();
customizeId = 'nav_menu[' + String( placeholderId ) + ']';
// Register the menu control setting.
setting = api.create( customizeId, customizeId, {}, {
transport: api.Menus.data.settingTransport,
api.Menus.data.defaultSettingValues.nav_menu,
* Add the menu section (and its controls).
* Note that this will automatically create the required controls
* inside via the Section's ready method.
return api.section.add( new api.Menus.MenuSection( customizeId, {
title: displayNavMenuName( name ),
customizeAction: api.Menus.data.l10n.customizingMenus,
* wp.customize.Menus.NewMenuSection
* Customizer section for new menus.
* @class wp.customize.Menus.NewMenuSection
* @augments wp.customize.Section
api.Menus.NewMenuSection = api.Section.extend(/** @lends wp.customize.Menus.NewMenuSection.prototype */{
* Add behaviors for the accordion section.
attachEvents: function() {
container = section.container,
contentContainer = section.contentContainer,
navMenuSettingPattern = /^nav_menu\[/;
section.headContainer.find( '.accordion-section-title' ).replaceWith(
wp.template( 'nav-menu-create-menu-section-title' )
* We have to manually handle section expanded because we do not
* apply the `accordion-section-title` class to this button-driven section.
container.on( 'click', '.customize-add-menu-button', function() {
contentContainer.on( 'keydown', '.menu-name-field', function( event ) {
if ( 13 === event.which ) { // Enter.
contentContainer.on( 'click', '#customize-new-menu-submit', function( event ) {
* Get number of non-deleted nav menus.
* @return {number} Count.
function getNavMenuCount() {
api.each( function( setting ) {
if ( navMenuSettingPattern.test( setting.id ) && false !== setting.get() ) {
* Update visibility of notice to prompt users to create menus.
function updateNoticeVisibility() {
container.find( '.add-new-menu-notice' ).prop( 'hidden', getNavMenuCount() > 0 );
* Handle setting addition.
* @param {wp.customize.Setting} setting - Added setting.
function addChangeEventListener( setting ) {
if ( navMenuSettingPattern.test( setting.id ) ) {
setting.bind( updateNoticeVisibility );
updateNoticeVisibility();
* Handle setting removal.
* @param {wp.customize.Setting} setting - Removed setting.
function removeChangeEventListener( setting ) {
if ( navMenuSettingPattern.test( setting.id ) ) {
setting.unbind( updateNoticeVisibility );
updateNoticeVisibility();
api.each( addChangeEventListener );
api.bind( 'add', addChangeEventListener );
api.bind( 'removed', removeChangeEventListener );
updateNoticeVisibility();
api.Section.prototype.attachEvents.apply( section, arguments );
* Create the controls for this section.
populateControls: function() {
menuNameControlId = section.id + '[name]';
menuNameControl = api.control( menuNameControlId );
if ( ! menuNameControl ) {
menuNameControl = new api.controlConstructor.nav_menu_name( menuNameControlId, {
label: api.Menus.data.l10n.menuNameLabel,
description: api.Menus.data.l10n.newMenuNameDescription,
api.control.add( menuNameControl.id, menuNameControl );
menuNameControl.active.set( true );
menuLocationsControlId = section.id + '[locations]';
menuLocationsControl = api.control( menuLocationsControlId );
if ( ! menuLocationsControl ) {
menuLocationsControl = new api.controlConstructor.nav_menu_locations( menuLocationsControlId, {
api.control.add( menuLocationsControlId, menuLocationsControl );
menuLocationsControl.active.set( true );
newMenuSubmitControlId = section.id + '[submit]';
newMenuSubmitControl = api.control( newMenuSubmitControlId );
if ( !newMenuSubmitControl ) {
newMenuSubmitControl = new api.Control( newMenuSubmitControlId, {
templateId: 'nav-menu-submit-new-button'
api.control.add( newMenuSubmitControlId, newMenuSubmitControl );
newMenuSubmitControl.active.set( true );
* Create the new menu with name and location supplied by the user.
contentContainer = section.contentContainer,
nameInput = contentContainer.find( '.menu-name-field' ).first(),
nameInput.addClass( 'invalid' );
menuSection = api.Menus.createNavMenu( name );
nameInput.removeClass( 'invalid' );
contentContainer.find( '.assigned-menu-location input[type=checkbox]' ).each( function() {
var checkbox = $( this ),
if ( checkbox.prop( 'checked' ) ) {
navMenuLocationSetting = api( 'nav_menu_locations[' + checkbox.data( 'location-id' ) + ']' );
navMenuLocationSetting.set( menuSection.params.menu_id );
// Reset state for next new menu.
checkbox.prop( 'checked', false );
wp.a11y.speak( api.Menus.data.l10n.menuAdded );
// Focus on the new menu section.
completeCallback: function() {
menuSection.highlightNewItemButton();
* Select a default location.
* This method selects a single location by default so we can support
* creating a menu for a specific menu location.
* @param {string|null} locationId - The ID of the location to select. `null` clears all selections.
selectDefaultLocation: function( locationId ) {
var locationControl = api.control( this.id + '[locations]' ),
if ( locationId !== null ) {
locationSelections[ locationId ] = true;
locationControl.setSelections( locationSelections );
* wp.customize.Menus.MenuLocationControl
* Customizer control for menu locations (rendered as a <select>).
* Note that 'nav_menu_location' must match the WP_Customize_Nav_Menu_Location_Control::$type.
* @class wp.customize.Menus.MenuLocationControl
* @augments wp.customize.Control
api.Menus.MenuLocationControl = api.Control.extend(/** @lends wp.customize.Menus.MenuLocationControl.prototype */{
initialize: function( id, options ) {
matches = id.match( /^nav_menu_locations\[(.+?)]/ );
control.themeLocation = matches[1];
api.Control.prototype.initialize.call( control, id, options );
var control = this, navMenuIdRegex = /^nav_menu\[(-?\d+)]/;
// @todo It would be better if this was added directly on the setting itself, as opposed to the control.
control.setting.validate = function( value ) {
return parseInt( value, 10 );
// Create and Edit menu buttons.
control.container.find( '.create-menu' ).on( 'click', function() {
var addMenuSection = api.section( 'add_menu' );
addMenuSection.selectDefaultLocation( this.dataset.locationId );