: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param {Object} [options] Options.
* @param {boolean} [options.unsaved=false] Whether only values not saved yet into a changeset will be returned (differential changes).
* @return {Object} Dirty setting values.
api.dirtyValues = function dirtyValues( options ) {
api.each( function( setting ) {
if ( ! setting._dirty ) {
settingRevision = api._latestSettingRevisions[ setting.id ];
// Skip including settings that have already been included in the changeset, if only requesting unsaved.
if ( api.state( 'changesetStatus' ).get() && ( options && options.unsaved ) && ( _.isUndefined( settingRevision ) || settingRevision <= api._lastSavedRevision ) ) {
values[ setting.id ] = setting.get();
* Request updates to the changeset.
* @alias wp.customize.requestChangesetUpdate
* @param {Object} [changes] - Mapping of setting IDs to setting params each normally including a value property, or mapping to null.
* If not provided, then the changes will still be obtained from unsaved dirty settings.
* @param {Object} [args] - Additional options for the save request.
* @param {boolean} [args.autosave=false] - Whether changes will be stored in autosave revision if the changeset has been promoted from an auto-draft.
* @param {boolean} [args.force=false] - Send request to update even when there are no changes to submit. This can be used to request the latest status of the changeset on the server.
* @param {string} [args.title] - Title to update in the changeset. Optional.
* @param {string} [args.date] - Date to update in the changeset. Optional.
* @return {jQuery.Promise} Promise resolving with the response data.
api.requestChangesetUpdate = function requestChangesetUpdate( changes, args ) {
var deferred, request, submittedChanges = {}, data, submittedArgs;
deferred = new $.Deferred();
// Prevent attempting changeset update while request is being made.
if ( 0 !== api.state( 'processing' ).get() ) {
deferred.reject( 'already_processing' );
return deferred.promise();
submittedArgs = _.extend( {
_.extend( submittedChanges, changes );
// Ensure all revised settings (changes pending save) are also included, but not if marked for deletion in changes.
_.each( api.dirtyValues( { unsaved: true } ), function( dirtyValue, settingId ) {
if ( ! changes || null !== changes[ settingId ] ) {
submittedChanges[ settingId ] = _.extend(
submittedChanges[ settingId ] || {},
// Allow plugins to attach additional params to the settings.
api.trigger( 'changeset-save', submittedChanges, submittedArgs );
// Short-circuit when there are no pending changes.
if ( ! submittedArgs.force && _.isEmpty( submittedChanges ) && null === submittedArgs.title && null === submittedArgs.date ) {
return deferred.promise();
// A status would cause a revision to be made, and for this wp.customize.previewer.save() should be used.
// Status is also disallowed for revisions regardless.
if ( submittedArgs.status ) {
return deferred.reject( { code: 'illegal_status_in_changeset_update' } ).promise();
// Dates not beung allowed for revisions are is a technical limitation of post revisions.
if ( submittedArgs.date && submittedArgs.autosave ) {
return deferred.reject( { code: 'illegal_autosave_with_date_gmt' } ).promise();
// Make sure that publishing a changeset waits for all changeset update requests to complete.
api.state( 'processing' ).set( api.state( 'processing' ).get() + 1 );
deferred.always( function() {
api.state( 'processing' ).set( api.state( 'processing' ).get() - 1 );
// Ensure that if any plugins add data to save requests by extending query() that they get included here.
data = api.previewer.query( { excludeCustomizedSaved: true } );
delete data.customized; // Being sent in customize_changeset_data instead.
nonce: api.settings.nonce.save,
customize_theme: api.settings.theme.stylesheet,
customize_changeset_data: JSON.stringify( submittedChanges )
if ( null !== submittedArgs.title ) {
data.customize_changeset_title = submittedArgs.title;
if ( null !== submittedArgs.date ) {
data.customize_changeset_date = submittedArgs.date;
if ( false !== submittedArgs.autosave ) {
data.customize_changeset_autosave = 'true';
// Allow plugins to modify the params included with the save request.
api.trigger( 'save-request-params', data );
request = wp.ajax.post( 'customize_save', data );
request.done( function requestChangesetUpdateDone( data ) {
var savedChangesetValues = {};
// Ensure that all settings updated subsequently will be included in the next changeset update request.
api._lastSavedRevision = Math.max( api._latestRevision, api._lastSavedRevision );
api.state( 'changesetStatus' ).set( data.changeset_status );
if ( data.changeset_date ) {
api.state( 'changesetDate' ).set( data.changeset_date );
deferred.resolve( data );
api.trigger( 'changeset-saved', data );
if ( data.setting_validities ) {
_.each( data.setting_validities, function( validity, settingId ) {
if ( true === validity && _.isObject( submittedChanges[ settingId ] ) && ! _.isUndefined( submittedChanges[ settingId ].value ) ) {
savedChangesetValues[ settingId ] = submittedChanges[ settingId ].value;
api.previewer.send( 'changeset-saved', _.extend( {}, data, { saved_changeset_values: savedChangesetValues } ) );
request.fail( function requestChangesetUpdateFail( data ) {
api.trigger( 'changeset-error', data );
request.always( function( data ) {
if ( data.setting_validities ) {
api._handleSettingValidities( {
settingValidities: data.setting_validities
return deferred.promise();
* Watch all changes to Value properties, and bubble changes to parent Values instance
* @alias wp.customize.utils.bubbleChildValueChanges
* @param {wp.customize.Class} instance
* @param {Array} properties The names of the Value instances to watch.
api.utils.bubbleChildValueChanges = function ( instance, properties ) {
$.each( properties, function ( i, key ) {
instance[ key ].bind( function ( to, from ) {
if ( instance.parent && to !== from ) {
instance.parent.trigger( 'change', instance );
* Expand a panel, section, or control and focus on the first focusable element.
* @alias wp.customize~focus
* @param {Object} [params]
* @param {Function} [params.completeCallback]
focus = function ( params ) {
var construct, completeCallback, focus, focusElement, sections;
// If a child section is currently expanded, collapse it.
if ( construct.extended( api.Panel ) ) {
sections = construct.sections();
if ( 1 < sections.length ) {
sections.forEach( function ( section ) {
if ( section.expanded() ) {
if ( ( construct.extended( api.Panel ) || construct.extended( api.Section ) ) && construct.expanded && construct.expanded() ) {
focusContainer = construct.contentContainer;
focusContainer = construct.container;
focusElement = focusContainer.find( '.control-focus:first' );
if ( 0 === focusElement.length ) {
// Note that we can't use :focusable due to a jQuery UI issue. See: https://github.com/jquery/jquery-ui/pull/1583
focusElement = focusContainer.find( 'input, select, textarea, button, object, a[href], [tabindex]' ).filter( ':visible' ).first();
if ( params.completeCallback ) {
completeCallback = params.completeCallback;
params.completeCallback = function () {
params.completeCallback = focus;
api.state( 'paneVisible' ).set( true );
if ( construct.expand ) {
construct.expand( params );
params.completeCallback();
* Stable sort for Panels, Sections, and Controls.
* If a.priority() === b.priority(), then sort by their respective params.instanceNumber.
* @alias wp.customize.utils.prioritySort
* @param {(wp.customize.Panel|wp.customize.Section|wp.customize.Control)} a
* @param {(wp.customize.Panel|wp.customize.Section|wp.customize.Control)} b
api.utils.prioritySort = function ( a, b ) {
if ( a.priority() === b.priority() && typeof a.params.instanceNumber === 'number' && typeof b.params.instanceNumber === 'number' ) {
return a.params.instanceNumber - b.params.instanceNumber;
return a.priority() - b.priority();
* Return whether the supplied Event object is for a keydown event but not the Enter key.
* @alias wp.customize.utils.isKeydownButNotEnterEvent
* @param {jQuery.Event} event
api.utils.isKeydownButNotEnterEvent = function ( event ) {
return ( 'keydown' === event.type && 13 !== event.which );
* Return whether the two lists of elements are the same and are in the same order.
* @alias wp.customize.utils.areElementListsEqual
* @param {Array|jQuery} listA
* @param {Array|jQuery} listB
api.utils.areElementListsEqual = function ( listA, listB ) {
listA.length === listB.length && // If lists are different lengths, then naturally they are not equal.
-1 === _.indexOf( _.map( // Are there any false values in the list returned by map?
_.zip( listA, listB ), // Pair up each element between the two lists.
return $( pair[0] ).is( pair[1] ); // Compare to see if each pair is equal.
), false ) // Check for presence of false in map's return value.
* Highlight the existence of a button.
* This function reminds the user of a button represented by the specified
* UI element, after an optional delay. If the user focuses the element
* before the delay passes, the reminder is canceled.
* @alias wp.customize.utils.highlightButton
* @param {jQuery} button - The element to highlight.
* @param {Object} [options] - Options.
* @param {number} [options.delay=0] - Delay in milliseconds.
* @param {jQuery} [options.focusTarget] - A target for user focus that defaults to the highlighted element.
* If the user focuses the target before the delay passes, the reminder
* is canceled. This option exists to accommodate compound buttons
* containing auxiliary UI, such as the Publish button augmented with a
* @return {Function} An idempotent function that cancels the reminder.
api.utils.highlightButton = function highlightButton( button, options ) {
var animationClass = 'button-see-me',
function cancelReminder() {
params.focusTarget.on( 'focusin', cancelReminder );
params.focusTarget.off( 'focusin', cancelReminder );
button.addClass( animationClass );
button.one( 'animationend', function() {
* Remove animation class to avoid situations in Customizer where
* DOM nodes are moved (re-inserted) and the animation repeats.
button.removeClass( animationClass );
* Get current timestamp adjusted for server clock time.
* Same functionality as the `current_time( 'mysql', false )` function in PHP.
* @alias wp.customize.utils.getCurrentTimestamp
* @return {number} Current timestamp.
api.utils.getCurrentTimestamp = function getCurrentTimestamp() {
var currentDate, currentClientTimestamp, timestampDifferential;
currentClientTimestamp = _.now();
currentDate = new Date( api.settings.initialServerDate.replace( /-/g, '/' ) );
timestampDifferential = currentClientTimestamp - api.settings.initialClientTimestamp;
timestampDifferential += api.settings.initialClientTimestamp - api.settings.initialServerTimestamp;
currentDate.setTime( currentDate.getTime() + timestampDifferential );
return currentDate.getTime();
* Get remaining time of when the date is set.
* @alias wp.customize.utils.getRemainingTime
* @param {string|number|Date} datetime - Date time or timestamp of the future date.
* @return {number} remainingTime - Remaining time in milliseconds.
api.utils.getRemainingTime = function getRemainingTime( datetime ) {
var millisecondsDivider = 1000, remainingTime, timestamp;
if ( datetime instanceof Date ) {
timestamp = datetime.getTime();
} else if ( 'string' === typeof datetime ) {
timestamp = ( new Date( datetime.replace( /-/g, '/' ) ) ).getTime();
remainingTime = timestamp - api.utils.getCurrentTimestamp();
remainingTime = Math.ceil( remainingTime / millisecondsDivider );
* Return browser supported `transitionend` event name.
* @return {string|null} Normalized `transitionend` event name or null if CSS transitions are not supported.
normalizedTransitionendEventName = (function () {
var el, transitions, prop;
el = document.createElement( 'div' );
'transition' : 'transitionend',
'OTransition' : 'oTransitionEnd',
'MozTransition' : 'transitionend',
'WebkitTransition': 'webkitTransitionEnd'
prop = _.find( _.keys( transitions ), function( prop ) {
return ! _.isUndefined( el.style[ prop ] );
return transitions[ prop ];
Container = api.Class.extend(/** @lends wp.customize~Container.prototype */{
defaultActiveArguments: { duration: 'fast', completeCallback: $.noop },
defaultExpandedArguments: { duration: 'fast', completeCallback: $.noop },
containerType: 'container',
* Base class for Panel and Section.
* @constructs wp.customize~Container
* @augments wp.customize.Class
* @borrows wp.customize~focus as focus
* @param {string} id - The ID for the container.
* @param {Object} options - Object containing one property: params.
* @param {string} options.title - Title shown when panel is collapsed and expanded.
* @param {string} [options.description] - Description shown at the top of the panel.
* @param {number} [options.priority=100] - The sort priority for the panel.
* @param {string} [options.templateId] - Template selector for container.
* @param {string} [options.type=default] - The type of the panel. See wp.customize.panelConstructor.
* @param {string} [options.content] - The markup to be used for the panel container. If empty, a JS template is used.
* @param {boolean} [options.active=true] - Whether the panel is active or not.
* @param {Object} [options.params] - Deprecated wrapper for the above properties.
initialize: function ( id, options ) {
if ( ! Container.instanceCounter ) {
Container.instanceCounter = 0;
Container.instanceCounter++;
options.params || options, // Passing the params is deprecated.
if ( ! container.params.instanceNumber ) {
container.params.instanceNumber = Container.instanceCounter;
container.notifications = new api.Notifications();
container.templateSelector = container.params.templateId || 'customize-' + container.containerType + '-' + container.params.type;
container.container = $( container.params.content );
if ( 0 === container.container.length ) {
container.container = $( container.getContainer() );
container.headContainer = container.container;
container.contentContainer = container.getContent();
container.container = container.container.add( container.contentContainer );
embedded: new $.Deferred()