: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$( '#post-body-content' ).on( 'change', '.menu-item-checkbox', function() {
var menuItemName, menuItemType, menuItemID, listedMenuItem;
if ( ! $( '.menu-items-delete' ).is( '[aria-describedby="pending-menu-items-to-delete"]' ) ) {
$( '.menu-items-delete' ).attr( 'aria-describedby', 'pending-menu-items-to-delete' );
menuItemName = $(this).next().text();
menuItemType = $(this).parent().next( '.item-controls' ).find( '.item-type' ).text();
menuItemID = $(this).attr( 'data-menu-item-id' );
listedMenuItem = $( '#pending-menu-items-to-delete ul' ).find( '[data-menu-item-id=' + menuItemID + ']' );
if ( listedMenuItem.length > 0 ) {
if ( this.checked === true ) {
const $li = $( '<li>', { 'data-menu-item-id': menuItemID } );
$li.append( $( '<span>', {
'class': 'pending-menu-item-name',
$li.append( $( '<span>', {
'class': 'pending-menu-item-type',
text: '(' + menuItemType + ')',
$li.append( $( '<span>', { 'class': 'separator' } ) );
$( '#pending-menu-items-to-delete ul' ).append( $li );
$( '#pending-menu-items-to-delete li .separator' ).html( ', ' );
$( '#pending-menu-items-to-delete li .separator' ).last().html( '.' );
* Set status of bulk delete checkbox.
setBulkDeleteCheckboxStatus : function() {
var checkbox = $( '#menu-to-edit .menu-item-checkbox' );
$.each( checkbox, function() {
if ( $(this).prop( 'disabled' ) ) {
$(this).prop( 'disabled', false );
$(this).prop( 'disabled', true );
if ( $(this).is( ':checked' ) ) {
$(this).prop( 'checked', false );
that.setRemoveSelectedButtonStatus();
* Set status of menu items removal button.
setRemoveSelectedButtonStatus : function() {
var button = $( '.menu-items-delete' );
if ( $( '.menu-item-checkbox:checked' ).length > 0 ) {
button.removeClass( 'disabled' );
button.addClass( 'disabled' );
attachMenuSaveSubmitListeners : function() {
* When a navigation menu is saved, store a JSON representation of all form data
* in a single input to avoid PHP `max_input_vars` limitations. See #14134.
$( '#update-nav-menu' ).on( 'submit', function() {
var navMenuData = $( '#update-nav-menu' ).serializeArray();
$( '[name="nav-menu-data"]' ).val( JSON.stringify( navMenuData ) );
attachThemeLocationsListeners : function() {
var loc = $('#nav-menu-theme-locations'), params = {};
params.action = 'menu-locations-save';
params['menu-settings-column-nonce'] = $('#menu-settings-column-nonce').val();
loc.find('input[type="submit"]').on( 'click', function() {
loc.find('select').each(function() {
params[this.name] = $(this).val();
loc.find( '.spinner' ).addClass( 'is-active' );
$.post( ajaxurl, params, function() {
loc.find( '.spinner' ).removeClass( 'is-active' );
attachQuickSearchListeners : function() {
// Prevent form submission.
$( '#nav-menu-meta' ).on( 'submit', function( event ) {
$( '#nav-menu-meta' ).on( 'input', '.quick-search', function() {
$this.attr( 'autocomplete', 'off' );
clearTimeout( searchTimer );
searchTimer = setTimeout( function() {
api.updateQuickSearchResults( $this );
}).on( 'blur', '.quick-search', function() {
updateQuickSearchResults : function(input) {
* Minimum characters for a search. Also avoid a new Ajax search when
* the pressed key (e.g. arrows) doesn't change the searched term.
if ( q.length < minSearchLength || api.lastSearch == q ) {
panel = input.parents('.tabs-panel');
'action': 'menu-quick-search',
'response-format': 'markup',
'menu': $('#menu').val(),
'menu-settings-column-nonce': $('#menu-settings-column-nonce').val(),
'type': input.attr('name')
$( '.spinner', panel ).addClass( 'is-active' );
$.post( ajaxurl, params, function(menuMarkup) {
api.processQuickSearchQueryResponse(menuMarkup, params, panel);
addCustomLink : function( processMethod ) {
var url = $('#custom-menu-item-url').val().toString(),
label = $('#custom-menu-item-name').val();
processMethod = processMethod || api.addMenuItemToBottom;
if ( '' === url || 'https://' == url || 'http://' == url ) {
$('#customlinkdiv').addClass('form-invalid');
// Show the Ajax spinner.
$( '.customlinkdiv .spinner' ).addClass( 'is-active' );
this.addLinkToMenu( url, label, processMethod, function() {
// Remove the Ajax spinner.
$( '.customlinkdiv .spinner' ).removeClass( 'is-active' );
// Set custom link form back to defaults.
$('#custom-menu-item-name').val('').trigger( 'blur' );
$( '#custom-menu-item-url' ).val( '' ).attr( 'placeholder', 'https://' );
addLinkToMenu : function(url, label, processMethod, callback) {
processMethod = processMethod || api.addMenuItemToBottom;
callback = callback || function(){};
'menu-item-type': 'custom',
}, processMethod, callback);
addItemToMenu : function(menuItem, processMethod, callback) {
var menu = $('#menu').val(),
nonce = $('#menu-settings-column-nonce').val(),
processMethod = processMethod || function(){};
callback = callback || function(){};
'action': 'add-menu-item',
'menu-settings-column-nonce': nonce,
$.post( ajaxurl, params, function(menuMarkup) {
var ins = $('#menu-instructions');
menuMarkup = menuMarkup || '';
menuMarkup = menuMarkup.toString().trim(); // Trim leading whitespaces.
processMethod(menuMarkup, params);
// Make it stand out a bit more visually, by adding a fadeIn.
$( 'li.pending' ).hide().fadeIn('slow');
$( '.drag-instructions' ).show();
if( ! ins.hasClass( 'menu-instructions-inactive' ) && ins.siblings().length )
ins.addClass( 'menu-instructions-inactive' );
* Process the add menu item request response into menu list item. Appends to menu.
* @param {string} menuMarkup The text server response of menu item markup.
* @fires document#menu-item-added Passes menuMarkup as a jQuery object.
addMenuItemToBottom : function( menuMarkup ) {
var $menuMarkup = $( menuMarkup );
$menuMarkup.hideAdvancedMenuItemFields().appendTo( api.targetList );
api.refreshKeyboardAccessibility();
api.refreshAdvancedAccessibility();
wp.a11y.speak( menus.itemAdded );
$( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
* Process the add menu item request response into menu list item. Prepends to menu.
* @param {string} menuMarkup The text server response of menu item markup.
* @fires document#menu-item-added Passes menuMarkup as a jQuery object.
addMenuItemToTop : function( menuMarkup ) {
var $menuMarkup = $( menuMarkup );
$menuMarkup.hideAdvancedMenuItemFields().prependTo( api.targetList );
api.refreshKeyboardAccessibility();
api.refreshAdvancedAccessibility();
wp.a11y.speak( menus.itemAdded );
$( document ).trigger( 'menu-item-added', [ $menuMarkup ] );
attachUnsavedChangesListener : function() {
$('#menu-management input, #menu-management select, #menu-management, #menu-management textarea, .menu-location-menus select').on( 'change', function(){
if ( 0 !== $('#menu-to-edit').length || 0 !== $('.menu-location-menus select').length ) {
window.onbeforeunload = function(){
return wp.i18n.__( 'The changes you made will be lost if you navigate away from this page.' );
// Make the post boxes read-only, as they can't be used yet.
$( '#menu-settings-column' ).find( 'input,select' ).end().find( 'a' ).attr( 'href', '#' ).off( 'click' );
registerChange : function() {
attachTabsPanelListeners : function() {
$('#menu-settings-column').on('click', function(e) {
var selectAreaMatch, selectAll, panelId, wrapper, items,
if ( target.hasClass('nav-tab-link') ) {
panelId = target.data( 'type' );
wrapper = target.parents('.accordion-section-content').first();
// Upon changing tabs, we want to uncheck all checkboxes.
$( 'input', wrapper ).prop( 'checked', false );
$('.tabs-panel-active', wrapper).removeClass('tabs-panel-active').addClass('tabs-panel-inactive');
$('#' + panelId, wrapper).removeClass('tabs-panel-inactive').addClass('tabs-panel-active');
$('.tabs', wrapper).removeClass('tabs');
target.parent().addClass('tabs');
// Select the search bar.
$('.quick-search', wrapper).trigger( 'focus' );
// Hide controls in the search tab if no items found.
if ( ! wrapper.find( '.tabs-panel-active .menu-item-title' ).length ) {
wrapper.addClass( 'has-no-menu-item' );
wrapper.removeClass( 'has-no-menu-item' );
} else if ( target.hasClass( 'select-all' ) ) {
selectAreaMatch = target.closest( '.button-controls' ).data( 'items-type' );
items = $( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' );
if ( items.length === items.filter( ':checked' ).length && ! target.is( ':checked' ) ) {
items.prop( 'checked', false );
} else if ( target.is( ':checked' ) ) {
items.prop( 'checked', true );
} else if ( target.hasClass( 'menu-item-checkbox' ) ) {
selectAreaMatch = target.closest( '.tabs-panel-active' ).parent().attr( 'id' );
items = $( '#' + selectAreaMatch + ' .tabs-panel-active .menu-item-title input' );
selectAll = $( '.button-controls[data-items-type="' + selectAreaMatch + '"] .select-all' );
if ( items.length === items.filter( ':checked' ).length && ! selectAll.is( ':checked' ) ) {
selectAll.prop( 'checked', true );
} else if ( selectAll.is( ':checked' ) ) {
selectAll.prop( 'checked', false );
} else if ( target.hasClass('submit-add-to-menu') ) {
if ( e.target.id && 'submit-customlinkdiv' == e.target.id )
api.addCustomLink( api.addMenuItemToBottom );
else if ( e.target.id && -1 != e.target.id.indexOf('submit-') )
$('#' + e.target.id.replace(/submit-/, '')).addSelectedToMenu( api.addMenuItemToBottom );
* Delegate the `click` event and attach it just to the pagination
* links thus excluding the current page `<span>`. See ticket #35577.
$( '#nav-menu-meta' ).on( 'click', 'a.page-numbers', function() {
var $container = $( this ).closest( '.inside' );
$.post( ajaxurl, this.href.replace( /.*\?/, '' ).replace( /action=([^&]*)/, '' ) + '&action=menu-get-metabox',
var metaBoxData = JSON.parse( resp ),
if ( -1 === resp.indexOf( 'replace-id' ) ) {
// Get the post type menu meta box to update.
toReplace = document.getElementById( metaBoxData['replace-id'] );
if ( ! metaBoxData.markup || ! toReplace ) {
// Update the post type menu meta box with new content from the response.
$container.html( metaBoxData.markup );
eventOnClickEditLink : function(clickedEl) {
matchedSection = /#(.*)$/.exec(clickedEl.href);
if ( matchedSection && matchedSection[1] ) {
settings = $('#'+matchedSection[1]);
item = settings.parent();
if( 0 !== item.length ) {
if( item.hasClass('menu-item-edit-inactive') ) {
if( ! settings.data('menu-item-data') ) {
settings.data( 'menu-item-data', settings.getItemData() );
settings.slideDown('fast');
item.removeClass('menu-item-edit-inactive')
.addClass('menu-item-edit-active');
settings.slideUp('fast');
item.removeClass('menu-item-edit-active')
.addClass('menu-item-edit-inactive');
eventOnClickCancelLink : function(clickedEl) {
var settings = $( clickedEl ).closest( '.menu-item-settings' ),
thisMenuItem = $( clickedEl ).closest( '.menu-item' );
thisMenuItem.removeClass( 'menu-item-edit-active' ).addClass( 'menu-item-edit-inactive' );
settings.setItemData( settings.data( 'menu-item-data' ) ).hide();
// Restore the title of the currently active/expanded menu item.
thisMenuItem.find( '.menu-item-title' ).text( settings.data( 'menu-item-data' )['menu-item-title'] );
eventOnClickMenuSave : function() {
var menuName = $('#menu-name'),
menuNameVal = menuName.val();
// Cancel and warn if invalid menu name.
if ( ! menuNameVal || ! menuNameVal.replace( /\s+/, '' ) ) {
menuName.parent().addClass( 'form-invalid' );
// Copy menu theme locations.
// Note: This appears to be dead code since #nav-menu-theme-locations no longer exists, perhaps removed in r32842.
var $updateNavMenu = $('#update-nav-menu');
$('#nav-menu-theme-locations select').each(function() {
// Update menu item position data.
api.menuList.find('.menu-item-data-position').val( function(index) { return index + 1; } );
window.onbeforeunload = null;
eventOnClickMenuDelete : function() {
if ( window.confirm( wp.i18n.__( 'You are about to permanently delete this menu.\n\'Cancel\' to stop, \'OK\' to delete.' ) ) ) {
window.onbeforeunload = null;
eventOnClickMenuItemDelete : function(clickedEl) {
var itemID = parseInt(clickedEl.id.replace('delete-', ''), 10);
api.removeMenuItem( $('#menu-item-' + itemID) );
* Process the quick search response into a search result
* @param string resp The server response to the query.
* @param object req The request arguments.
* @param jQuery panel The tabs panel we're searching in.
processQuickSearchQueryResponse : function(resp, req, panel) {
form = document.getElementById('nav-menu-meta'),
pattern = /menu-item[(\[^]\]*/,
$items = $('<div>').html(resp).find('li'),
wrapper = panel.closest( '.accordion-section-content' ),
selectAll = wrapper.find( '.button-controls .select-all' ),
const p = $( '<p>', { text: wp.i18n.__( 'No results found.' ) } );
$('.categorychecklist', panel).empty().append( li );
$( '.spinner', panel ).removeClass( 'is-active' );
wrapper.addClass( 'has-no-menu-item' );
// Make a unique DB ID number.
matched = pattern.exec($item.html());
if ( matched && matched[1] ) {
while( form.elements['menu-item[' + newID + '][menu-item-type]'] || takenIDs[ newID ] ) {