: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param {HTMLFormElement} form Form.
api.prepareFormPreview = function prepareFormPreview( form ) {
var urlParser, stateParams = {};
form.action = location.href;
urlParser = document.createElement( 'a' );
urlParser.href = form.action;
// Make sure forms in preview use HTTPS if parent frame uses HTTPS.
if ( api.settings.channel && 'https' === api.preview.scheme.get() && 'http:' === urlParser.protocol && -1 !== api.settings.url.allowedHosts.indexOf( urlParser.host ) ) {
urlParser.protocol = 'https:';
form.action = urlParser.href;
if ( 'GET' !== form.method.toUpperCase() || ! api.isLinkPreviewable( urlParser ) ) {
// Style form as unpreviewable only if previewing in iframe; if previewing on frontend, all forms will be allowed to work normally.
if ( api.settings.channel ) {
$( form ).addClass( 'customize-unpreviewable' );
$( form ).removeClass( 'customize-unpreviewable' );
stateParams.customize_changeset_uuid = api.settings.changeset.uuid;
if ( api.settings.changeset.autosaved ) {
stateParams.customize_autosaved = 'on';
if ( ! api.settings.theme.active ) {
stateParams.customize_theme = api.settings.theme.stylesheet;
if ( api.settings.channel ) {
stateParams.customize_messenger_channel = api.settings.channel;
_.each( stateParams, function( value, name ) {
var input = $( form ).find( 'input[name="' + name + '"]' );
$( form ).prepend( $( '<input>', {
// Prevent links from breaking out of preview iframe.
if ( api.settings.channel ) {
* Watch current URL and send keep-alive (heartbeat) messages to the parent.
* Keep the customizer pane notified that the preview is still alive
* and that the user hasn't navigated to a non-customized URL.
api.keepAliveCurrentUrl = ( function() {
var previousPathName = location.pathname,
previousQueryString = location.search.substr( 1 ),
previousQueryParams = null,
stateQueryParams = [ 'customize_theme', 'customize_changeset_uuid', 'customize_messenger_channel', 'customize_autosaved' ];
return function keepAliveCurrentUrl() {
var urlParser, currentQueryParams;
// Short-circuit with keep-alive if previous URL is identical (as is normal case).
if ( previousQueryString === location.search.substr( 1 ) && previousPathName === location.pathname ) {
api.preview.send( 'keep-alive' );
urlParser = document.createElement( 'a' );
if ( null === previousQueryParams ) {
urlParser.search = previousQueryString;
previousQueryParams = api.utils.parseQueryString( previousQueryString );
_.each( stateQueryParams, function( name ) {
delete previousQueryParams[ name ];
// Determine if current URL minus customized state params and URL hash.
urlParser.href = location.href;
currentQueryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
_.each( stateQueryParams, function( name ) {
delete currentQueryParams[ name ];
if ( previousPathName !== location.pathname || ! _.isEqual( previousQueryParams, currentQueryParams ) ) {
urlParser.search = $.param( currentQueryParams );
api.settings.url.self = urlParser.href;
api.preview.send( 'ready', {
currentUrl: api.settings.url.self,
activePanels: api.settings.activePanels,
activeSections: api.settings.activeSections,
activeControls: api.settings.activeControls,
settingValidities: api.settings.settingValidities
api.preview.send( 'keep-alive' );
previousQueryParams = currentQueryParams;
previousQueryString = location.search.substr( 1 );
previousPathName = location.pathname;
api.settingPreviewHandlers = {
* Preview changes to custom logo.
* @param {number} attachmentId Attachment ID for custom logo.
custom_logo: function( attachmentId ) {
$( 'body' ).toggleClass( 'wp-custom-logo', !! attachmentId );
* Preview changes to custom css.
* @param {string} value Custom CSS..
custom_css: function( value ) {
$( '#wp-custom-css' ).text( value );
* Preview changes to any of the background settings.
var css = '', settings = {};
_.each( ['color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment'], function( prop ) {
settings[ prop ] = api( 'background_' + prop );
* The body will support custom backgrounds if either the color or image are set.
* See get_body_class() in /wp-includes/post-template.php
$( document.body ).toggleClass( 'custom-background', !! ( settings.color() || settings.image() ) );
if ( settings.color() ) {
css += 'background-color: ' + settings.color() + ';';
if ( settings.image() ) {
css += 'background-image: url("' + settings.image() + '");';
css += 'background-size: ' + settings.size() + ';';
css += 'background-position: ' + settings.position_x() + ' ' + settings.position_y() + ';';
css += 'background-repeat: ' + settings.repeat() + ';';
css += 'background-attachment: ' + settings.attachment() + ';';
$( '#custom-background-css' ).text( 'body.custom-background { ' + css + ' }' );
var bg, setValue, handleUpdatedChangesetUuid;
api.settings = window._wpCustomizeSettings;
api.preview = new api.Preview({
url: window.location.href,
channel: api.settings.channel
api.addRequestPreviewing();
* Create/update a setting value.
* @param {string} id - Setting ID.
* @param {*} value - Setting value.
* @param {boolean} [createDirty] - Whether to create a setting as dirty. Defaults to false.
setValue = function( id, value, createDirty ) {
createDirty = createDirty || false;
setting = api.create( id, value, {
// Mark dynamically-created settings as dirty so they will get posted.
api.preview.bind( 'settings', function( values ) {
$.each( values, setValue );
api.preview.trigger( 'settings', api.settings.values );
$.each( api.settings._dirty, function( i, id ) {
api.preview.bind( 'setting', function( args ) {
setValue.apply( null, args.concat( createDirty ) );
api.preview.bind( 'sync', function( events ) {
* Delete any settings that already exist locally which haven't been
* modified in the controls while the preview was loading. This prevents
* situations where the JS value being synced from the pane may differ
* from the PHP-sanitized JS value in the preview which causes the
* non-sanitized JS value to clobber the PHP-sanitized value. This
* is particularly important for selective refresh partials that
* have a fallback refresh behavior since infinite refreshing would
if ( events.settings && events['settings-modified-while-loading'] ) {
_.each( _.keys( events.settings ), function( syncedSettingId ) {
if ( api.has( syncedSettingId ) && ! events['settings-modified-while-loading'][ syncedSettingId ] ) {
delete events.settings[ syncedSettingId ];
$.each( events, function( event, args ) {
api.preview.trigger( event, args );
api.preview.send( 'synced' );
api.preview.bind( 'active', function() {
api.preview.send( 'nonce', api.settings.nonce );
api.preview.send( 'documentTitle', document.title );
// Send scroll in case of loading via non-refresh.
api.preview.send( 'scroll', $( window ).scrollTop() );
* Handle update to changeset UUID.
* @param {string} uuid - UUID.
handleUpdatedChangesetUuid = function( uuid ) {
api.settings.changeset.uuid = uuid;
// Update UUIDs in links and forms.
$( document.body ).find( 'a[href], area[href]' ).each( function() {
api.prepareLinkPreview( this );
$( document.body ).find( 'form' ).each( function() {
api.prepareFormPreview( this );
* Replace the UUID in the URL. Note that the wrapped history.replaceState()
* will handle injecting the current api.settings.changeset.uuid into the URL,
* so this is merely to trigger that logic.
if ( history.replaceState ) {
history.replaceState( currentHistoryState, '', location.href );
api.preview.bind( 'changeset-uuid', handleUpdatedChangesetUuid );
api.preview.bind( 'saved', function( response ) {
if ( response.next_changeset_uuid ) {
handleUpdatedChangesetUuid( response.next_changeset_uuid );
api.trigger( 'saved', response );
// Update the URLs to reflect the fact we've started autosaving.
api.preview.bind( 'autosaving', function() {
if ( api.settings.changeset.autosaved ) {
api.settings.changeset.autosaved = true; // Start deferring to any autosave once changeset is updated.
$( document.body ).find( 'a[href], area[href]' ).each( function() {
api.prepareLinkPreview( this );
$( document.body ).find( 'form' ).each( function() {
api.prepareFormPreview( this );
if ( history.replaceState ) {
history.replaceState( currentHistoryState, '', location.href );
* Clear dirty flag for settings when saved to changeset so that they
* won't be needlessly included in selective refresh or ajax requests.
api.preview.bind( 'changeset-saved', function( data ) {
_.each( data.saved_changeset_values, function( value, settingId ) {
var setting = api( settingId );
if ( setting && _.isEqual( setting.get(), value ) ) {
api.preview.bind( 'nonce-refresh', function( nonce ) {
$.extend( api.settings.nonce, nonce );
* Send a message to the parent customize frame with a list of which
* containers and controls are active.
api.preview.send( 'ready', {
currentUrl: api.settings.url.self,
activePanels: api.settings.activePanels,
activeSections: api.settings.activeSections,
activeControls: api.settings.activeControls,
settingValidities: api.settings.settingValidities
// Send ready when URL changes via JS.
setInterval( api.keepAliveCurrentUrl, api.settings.timeouts.keepAliveSend );
// Display a loading indicator when preview is reloading, and remove on failure.
api.preview.bind( 'loading-initiated', function () {
$( 'body' ).addClass( 'wp-customizer-unloading' );
api.preview.bind( 'loading-failed', function () {
$( 'body' ).removeClass( 'wp-customizer-unloading' );
bg = $.map( ['color', 'image', 'preset', 'position_x', 'position_y', 'size', 'repeat', 'attachment'], function( prop ) {
return 'background_' + prop;
api.when.apply( api, bg ).done( function() {
$.each( arguments, function() {
this.bind( api.settingPreviewHandlers.background );
* Toggle the wp-custom-logo body class when a logo is added or removed.
api( 'custom_logo', function ( setting ) {
api.settingPreviewHandlers.custom_logo.call( setting, setting.get() );
setting.bind( api.settingPreviewHandlers.custom_logo );
api( 'custom_css[' + api.settings.theme.stylesheet + ']', function( setting ) {
setting.bind( api.settingPreviewHandlers.custom_css );
api.trigger( 'preview-ready' );