: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Focus on the first invalid control.
if ( ! wasFocused && ! _.isEmpty( invalidSettingControls ) ) {
_.values( invalidSettingControls )[0][0].focus();
* Find all controls associated with the given settings.
* @alias wp.customize.findControlsForSettings
* @param {string[]} settingIds Setting IDs.
* @return {Object<string, wp.customize.Control>} Mapping setting ids to arrays of controls.
api.findControlsForSettings = function findControlsForSettings( settingIds ) {
var controls = {}, settingControls;
_.each( _.unique( settingIds ), function( settingId ) {
var setting = api( settingId );
settingControls = setting.findControls();
if ( settingControls && settingControls.length > 0 ) {
controls[ settingId ] = settingControls;
* Sort panels, sections, controls by priorities. Hide empty sections and panels.
* @alias wp.customize.reflowPaneContents
api.reflowPaneContents = _.bind( function () {
var appendContainer, activeElement, rootHeadContainers, rootNodes = [], wasReflowed = false;
if ( document.activeElement ) {
activeElement = $( document.activeElement );
// Sort the sections within each panel.
api.panel.each( function ( panel ) {
if ( 'themes' === panel.id ) {
return; // Don't reflow theme sections, as doing so moves them after the themes container.
var sections = panel.sections(),
sectionHeadContainers = _.pluck( sections, 'headContainer' );
appendContainer = ( panel.contentContainer.is( 'ul' ) ) ? panel.contentContainer : panel.contentContainer.find( 'ul:first' );
if ( ! api.utils.areElementListsEqual( sectionHeadContainers, appendContainer.children( '[id]' ) ) ) {
_( sections ).each( function ( section ) {
appendContainer.append( section.headContainer );
// Sort the controls within each section.
api.section.each( function ( section ) {
var controls = section.controls(),
controlContainers = _.pluck( controls, 'container' );
if ( ! section.panel() ) {
rootNodes.push( section );
appendContainer = ( section.contentContainer.is( 'ul' ) ) ? section.contentContainer : section.contentContainer.find( 'ul:first' );
if ( ! api.utils.areElementListsEqual( controlContainers, appendContainer.children( '[id]' ) ) ) {
_( controls ).each( function ( control ) {
appendContainer.append( control.container );
// Sort the root panels and sections.
rootNodes.sort( api.utils.prioritySort );
rootHeadContainers = _.pluck( rootNodes, 'headContainer' );
appendContainer = $( '#customize-theme-controls .customize-pane-parent' ); // @todo This should be defined elsewhere, and to be configurable.
if ( ! api.utils.areElementListsEqual( rootHeadContainers, appendContainer.children() ) ) {
_( rootNodes ).each( function ( rootNode ) {
appendContainer.append( rootNode.headContainer );
// Now re-trigger the active Value callbacks so that the panels and sections can decide whether they can be rendered.
api.panel.each( function ( panel ) {
var value = panel.active();
panel.active.callbacks.fireWith( panel.active, [ value, value ] );
api.section.each( function ( section ) {
var value = section.active();
section.active.callbacks.fireWith( section.active, [ value, value ] );
// Restore focus if there was a reflow and there was an active (focused) element.
if ( wasReflowed && activeElement ) {
activeElement.trigger( 'focus' );
api.trigger( 'pane-contents-reflowed' );
api.state = new api.Values();
'selectedChangesetStatus',
'remainingTimeToPublish',
'editShortcutVisibility',
api.state.create( name );
api.settings = window._wpCustomizeSettings;
api.l10n = window._wpCustomizeControlsL10n;
// Check if we can run the Customizer.
// Bail if any incompatibilities are found.
if ( ! $.support.postMessage || ( ! $.support.cors && api.settings.isCrossDomain ) ) {
if ( null === api.PreviewFrame.prototype.sensitivity ) {
api.PreviewFrame.prototype.sensitivity = api.settings.timeouts.previewFrameSensitivity;
if ( null === api.Previewer.prototype.refreshBuffer ) {
api.Previewer.prototype.refreshBuffer = api.settings.timeouts.windowRefresh;
body = $( document.body ),
overlay = body.children( '.wp-full-overlay' ),
title = $( '#customize-info .panel-title.site-title' ),
closeBtn = $( '.customize-controls-close' ),
btnWrapper = $( '#customize-save-button-wrapper' ),
publishSettingsBtn = $( '#publish-settings' ),
footerActions = $( '#customize-footer-actions' );
// Add publish settings section in JS instead of PHP since the Customizer depends on it to function.
api.bind( 'ready', function() {
api.section.add( new api.OuterSection( 'publish_settings', {
title: api.l10n.publishSettings,
active: api.settings.theme.active
// Set up publish settings section and its controls.
api.section( 'publish_settings', function( section ) {
var updateButtonsState, trashControl, updateSectionActive, isSectionActive, statusControl, dateControl, toggleDateControl, publishWhenTime, pollInterval, updateTimeArrivedPoller, cancelScheduleButtonReminder, timeArrivedPollingInterval = 1000;
trashControl = new api.Control( 'trash_changeset', {
'class': 'button-link button-link-delete',
value: api.l10n.discardChanges
api.control.add( trashControl );
trashControl.deferred.embedded.done( function() {
trashControl.container.find( '.button-link' ).on( 'click', function() {
if ( confirm( api.l10n.trashConfirm ) ) {
wp.customize.previewer.trash();
api.control.add( new api.PreviewLinkControl( 'changeset_preview_link', {
* Return whether the publish settings section should be active.
* @return {boolean} Is section active.
isSectionActive = function() {
if ( ! api.state( 'activated' ).get() ) {
if ( api.state( 'trashing' ).get() || 'trash' === api.state( 'changesetStatus' ).get() ) {
if ( '' === api.state( 'changesetStatus' ).get() && api.state( 'saved' ).get() ) {
// Make sure publish settings are not available while the theme is not active and the customizer is in a published state.
section.active.validate = isSectionActive;
updateSectionActive = function() {
section.active.set( isSectionActive() );
api.state( 'activated' ).bind( updateSectionActive );
api.state( 'trashing' ).bind( updateSectionActive );
api.state( 'saved' ).bind( updateSectionActive );
api.state( 'changesetStatus' ).bind( updateSectionActive );
// Bind visibility of the publish settings button to whether the section is active.
updateButtonsState = function() {
publishSettingsBtn.toggle( section.active.get() );
saveBtn.toggleClass( 'has-next-sibling', section.active.get() );
section.active.bind( updateButtonsState );
function highlightScheduleButton() {
if ( ! cancelScheduleButtonReminder ) {
cancelScheduleButtonReminder = api.utils.highlightButton( btnWrapper, {
* Only abort the reminder when the save button is focused.
* If the user clicks the settings button to toggle the
* settings closed, we'll still remind them.
function cancelHighlightScheduleButton() {
if ( cancelScheduleButtonReminder ) {
cancelScheduleButtonReminder();
cancelScheduleButtonReminder = null;
api.state( 'selectedChangesetStatus' ).bind( cancelHighlightScheduleButton );
section.contentContainer.find( '.customize-action' ).text( api.l10n.updating );
section.contentContainer.find( '.customize-section-back' ).removeAttr( 'tabindex' );
publishSettingsBtn.prop( 'disabled', false );
publishSettingsBtn.on( 'click', function( event ) {
section.expanded.set( ! section.expanded.get() );
section.expanded.bind( function( isExpanded ) {
var defaultChangesetStatus;
publishSettingsBtn.attr( 'aria-expanded', String( isExpanded ) );
publishSettingsBtn.toggleClass( 'active', isExpanded );
cancelHighlightScheduleButton();
defaultChangesetStatus = api.state( 'changesetStatus' ).get();
if ( '' === defaultChangesetStatus || 'auto-draft' === defaultChangesetStatus ) {
defaultChangesetStatus = 'publish';
if ( api.state( 'selectedChangesetStatus' ).get() !== defaultChangesetStatus ) {
highlightScheduleButton();
} else if ( 'future' === api.state( 'selectedChangesetStatus' ).get() && api.state( 'selectedChangesetDate' ).get() !== api.state( 'changesetDate' ).get() ) {
highlightScheduleButton();
statusControl = new api.Control( 'changeset_status', {
section: 'publish_settings',
setting: api.state( 'selectedChangesetStatus' ),
templateId: 'customize-selected-changeset-status-control',
choices: api.settings.changeset.statusChoices
api.control.add( statusControl );
dateControl = new api.DateTimeControl( 'changeset_scheduled_date', {
section: 'publish_settings',
setting: api.state( 'selectedChangesetDate' ),
minYear: ( new Date() ).getFullYear(),
twelveHourFormat: /a/i.test( api.settings.timeFormat ),
description: api.l10n.scheduleDescription
dateControl.notifications.alt = true;
api.control.add( dateControl );
publishWhenTime = function() {
api.state( 'selectedChangesetStatus' ).set( 'publish' );
// Start countdown for when the dateTime arrives, or clear interval when it is .
updateTimeArrivedPoller = function() {
'future' === api.state( 'changesetStatus' ).get() &&
'future' === api.state( 'selectedChangesetStatus' ).get() &&
api.state( 'changesetDate' ).get() &&
api.state( 'selectedChangesetDate' ).get() === api.state( 'changesetDate' ).get() &&
api.utils.getRemainingTime( api.state( 'changesetDate' ).get() ) >= 0
if ( shouldPoll && ! pollInterval ) {
pollInterval = setInterval( function() {
var remainingTime = api.utils.getRemainingTime( api.state( 'changesetDate' ).get() );
api.state( 'remainingTimeToPublish' ).set( remainingTime );
if ( remainingTime <= 0 ) {
clearInterval( pollInterval );
}, timeArrivedPollingInterval );
} else if ( ! shouldPoll && pollInterval ) {
clearInterval( pollInterval );
api.state( 'changesetDate' ).bind( updateTimeArrivedPoller );
api.state( 'selectedChangesetDate' ).bind( updateTimeArrivedPoller );
api.state( 'changesetStatus' ).bind( updateTimeArrivedPoller );
api.state( 'selectedChangesetStatus' ).bind( updateTimeArrivedPoller );
updateTimeArrivedPoller();
// Ensure dateControl only appears when selected status is future.
dateControl.active.validate = function() {
return 'future' === api.state( 'selectedChangesetStatus' ).get();
toggleDateControl = function( value ) {
dateControl.active.set( 'future' === value );
toggleDateControl( api.state( 'selectedChangesetStatus' ).get() );
api.state( 'selectedChangesetStatus' ).bind( toggleDateControl );
// Show notification on date control when status is future but it isn't a future date.
api.state( 'saving' ).bind( function( isSaving ) {
if ( isSaving && 'future' === api.state( 'selectedChangesetStatus' ).get() ) {
dateControl.toggleFutureDateNotification( ! dateControl.isFutureDate() );
// Prevent the form from saving when enter is pressed on an input or select element.
$('#customize-controls').on( 'keydown', function( e ) {
var isEnter = ( 13 === e.which ),
if ( isEnter && ( $el.is( 'input:not([type=button])' ) || $el.is( 'select' ) ) ) {
// Expand/Collapse the main customizer customize info.
$( '.customize-info' ).find( '> .accordion-section-title .customize-help-toggle' ).on( 'click', function() {
var section = $( this ).closest( '.accordion-section' ),
content = section.find( '.customize-panel-description:first' );
if ( section.hasClass( 'cannot-expand' ) ) {
if ( section.hasClass( 'open' ) ) {
section.toggleClass( 'open' );
content.slideUp( api.Panel.prototype.defaultExpandedArguments.duration, function() {
content.trigger( 'toggled' );
$( this ).attr( 'aria-expanded', false );
content.slideDown( api.Panel.prototype.defaultExpandedArguments.duration, function() {
content.trigger( 'toggled' );
section.toggleClass( 'open' );
$( this ).attr( 'aria-expanded', true );
* @alias wp.customize.previewer
api.previewer = new api.Previewer({
container: '#customize-preview',
form: '#customize-controls',
previewUrl: api.settings.url.preview,
allowedUrls: api.settings.url.allowed
},/** @lends wp.customize.previewer */{
nonce: api.settings.nonce,
* Build the query to send along with the Preview request.
* @since 4.7.0 Added options param.
* @param {Object} [options] Options.
* @param {boolean} [options.excludeCustomizedSaved=false] Exclude saved settings in customized response (values pending writing to changeset).
* @return {Object} Query vars.
query: function( options ) {
customize_theme: api.settings.theme.stylesheet,
nonce: this.nonce.preview,
customize_changeset_uuid: api.settings.changeset.uuid
if ( api.settings.changeset.autosaved || ! api.state( 'saved' ).get() ) {
queryVars.customize_autosaved = 'on';
* Exclude customized data if requested especially for calls to requestChangesetUpdate.
* Changeset updates are differential and so it is a performance waste to send all of
* the dirty settings with each update.
queryVars.customized = JSON.stringify( api.dirtyValues( {
unsaved: options && options.excludeCustomizedSaved
* Save (and publish) the customizer changeset.
* Updates to the changeset are transactional. If any of the settings
* are invalid then none of them will be written into the changeset.
* A revision will be made for the changeset post if revisions support
* has been added to the post type.
* @since 4.7.0 Added args param and return value.
* @param {Object} [args] Args.
* @param {string} [args.status=publish] Status.
* @param {string} [args.date] Date, in local time in MySQL format.
* @param {string} [args.title] Title
* @return {jQuery.promise} Promise.
changesetStatus = api.state( 'selectedChangesetStatus' ).get(),
selectedChangesetDate = api.state( 'selectedChangesetDate' ).get(),
processing = api.state( 'processing' ),
submitWhenDoneProcessing,
modifiedWhileSaving = {},
invalidSettingLessControls = [];
if ( args && args.status ) {
changesetStatus = args.status;
if ( api.state( 'saving' ).get() ) {
deferred.reject( 'already_saving' );
api.state( 'saving' ).set( true );
function captureSettingModifiedDuringSave( setting ) {
modifiedWhileSaving[ setting.id ] = true;
var request, query, settingInvalidities = {}, latestRevision = api._latestRevision, errorCode = 'client_side_error';
api.bind( 'change', captureSettingModifiedDuringSave );