: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
isProcessingComplete = function() {
return 0 === api.state( 'processing' ).get();
if ( isProcessingComplete() ) {
originalRefresh.call( previewer );
refreshOnceProcessingComplete = function() {
if ( isProcessingComplete() ) {
originalRefresh.call( previewer );
api.state( 'processing' ).unbind( refreshOnceProcessingComplete );
api.state( 'processing' ).bind( refreshOnceProcessingComplete );
}( previewer.refresh ) ),
previewer.container = api.ensure( params.container );
previewer.allowedUrls = params.allowedUrls;
params.url = window.location.href;
api.Messenger.prototype.initialize.call( previewer, params );
urlParser.href = previewer.origin();
previewer.add( 'scheme', urlParser.protocol.replace( /:$/, '' ) );
* Limit the URL to internal, front-end links.
* If the front end and the admin are served from the same domain, load the
* preview over ssl if the Customizer is being loaded over ssl. This avoids
* insecure content warnings. This is not attempted if the admin and front end
* are on different domains to avoid the case where the front end doesn't have
previewer.add( 'previewUrl', params.previewUrl ).setter( function( to ) {
var result = null, urlParser, queryParams, parsedAllowedUrl, parsedCandidateUrls = [];
urlParser = document.createElement( 'a' );
// Abort if URL is for admin or (static) files in wp-includes or wp-content.
if ( /\/wp-(admin|includes|content)(\/|$)/.test( urlParser.pathname ) ) {
// Remove state query params.
if ( urlParser.search.length > 1 ) {
queryParams = api.utils.parseQueryString( urlParser.search.substr( 1 ) );
delete queryParams.customize_changeset_uuid;
delete queryParams.customize_theme;
delete queryParams.customize_messenger_channel;
delete queryParams.customize_autosaved;
if ( _.isEmpty( queryParams ) ) {
urlParser.search = $.param( queryParams );
parsedCandidateUrls.push( urlParser );
// Prepend list with URL that matches the scheme/protocol of the iframe.
if ( previewer.scheme.get() + ':' !== urlParser.protocol ) {
urlParser = document.createElement( 'a' );
urlParser.href = parsedCandidateUrls[0].href;
urlParser.protocol = previewer.scheme.get() + ':';
parsedCandidateUrls.unshift( urlParser );
// Attempt to match the URL to the control frame's scheme and check if it's allowed. If not, try the original URL.
parsedAllowedUrl = document.createElement( 'a' );
_.find( parsedCandidateUrls, function( parsedCandidateUrl ) {
return ! _.isUndefined( _.find( previewer.allowedUrls, function( allowedUrl ) {
parsedAllowedUrl.href = allowedUrl;
if ( urlParser.protocol === parsedAllowedUrl.protocol && urlParser.host === parsedAllowedUrl.host && 0 === urlParser.pathname.indexOf( parsedAllowedUrl.pathname.replace( /\/$/, '' ) ) ) {
result = parsedCandidateUrl.href;
previewer.bind( 'ready', previewer.ready );
// Start listening for keep-alive messages when iframe first loads.
previewer.deferred.active.done( _.bind( previewer.keepPreviewAlive, previewer ) );
previewer.bind( 'synced', function() {
previewer.send( 'active' );
// Refresh the preview when the URL is changed (but not yet).
previewer.previewUrl.bind( previewer.refresh );
previewer.bind( 'scroll', function( distance ) {
previewer.scroll = distance;
// Update the URL when the iframe sends a URL message, resetting scroll position. If URL is unchanged, then refresh.
previewer.bind( 'url', function( url ) {
var onUrlChange, urlChanged = false;
onUrlChange = function() {
previewer.previewUrl.bind( onUrlChange );
previewer.previewUrl.set( url );
previewer.previewUrl.unbind( onUrlChange );
// Update the document title when the preview changes.
previewer.bind( 'documentTitle', function ( title ) {
api.setDocumentTitle( title );
* Handle the preview receiving the ready message.
* @param {Object} data - Data from preview.
* @param {string} data.currentUrl - Current URL.
* @param {Object} data.activePanels - Active panels.
* @param {Object} data.activeSections Active sections.
* @param {Object} data.activeControls Active controls.
ready: function( data ) {
var previewer = this, synced = {}, constructs;
synced.settings = api.get();
synced['settings-modified-while-loading'] = previewer.settingsModifiedWhileLoading;
if ( 'resolved' !== previewer.deferred.active.state() || previewer.loading ) {
synced.scroll = previewer.scroll;
synced['edit-shortcut-visibility'] = api.state( 'editShortcutVisibility' ).get();
previewer.send( 'sync', synced );
// Set the previewUrl without causing the url to set the iframe.
previewer.previewUrl.unbind( previewer.refresh );
previewer.previewUrl.set( data.currentUrl );
previewer.previewUrl.bind( previewer.refresh );
* Walk over all panels, sections, and controls and set their
* respective active states to true if the preview explicitly
panel: data.activePanels,
section: data.activeSections,
control: data.activeControls
_( constructs ).each( function ( activeConstructs, type ) {
api[ type ].each( function ( construct, id ) {
var isDynamicallyCreated = _.isUndefined( api.settings[ type + 's' ][ id ] );
* If the construct was created statically in PHP (not dynamically in JS)
* then consider a missing (undefined) value in the activeConstructs to
* mean it should be deactivated (since it is gone). But if it is
* dynamically created then only toggle activation if the value is defined,
* as this means that the construct was also then correspondingly
* created statically in PHP and the active callback is available.
* Otherwise, dynamically-created constructs should normally have
* their active states toggled in JS rather than from PHP.
if ( ! isDynamicallyCreated || ! _.isUndefined( activeConstructs[ id ] ) ) {
if ( activeConstructs[ id ] ) {
if ( data.settingValidities ) {
api._handleSettingValidities( {
settingValidities: data.settingValidities,
focusInvalidControl: false
* Keep the preview alive by listening for ready and keep-alive messages.
* If a message is not received in the allotted time then the iframe will be set back to the last known valid URL.
keepPreviewAlive: function keepPreviewAlive() {
var previewer = this, keepAliveTick, timeoutId, handleMissingKeepAlive, scheduleKeepAliveCheck;
* Schedule a preview keep-alive check.
* Note that if a page load takes longer than keepAliveCheck milliseconds,
* the keep-alive messages will still be getting sent from the previous
scheduleKeepAliveCheck = function() {
timeoutId = setTimeout( handleMissingKeepAlive, api.settings.timeouts.keepAliveCheck );
* Set the previewerAlive state to true when receiving a message from the preview.
keepAliveTick = function() {
api.state( 'previewerAlive' ).set( true );
clearTimeout( timeoutId );
scheduleKeepAliveCheck();
* Set the previewerAlive state to false if keepAliveCheck milliseconds have transpired without a message.
* This is most likely to happen in the case of a connectivity error, or if the theme causes the browser
* to navigate to a non-allowed URL. Setting this state to false will force settings with a postMessage
* transport to use refresh instead, causing the preview frame also to be replaced with the current
handleMissingKeepAlive = function() {
api.state( 'previewerAlive' ).set( false );
scheduleKeepAliveCheck();
previewer.bind( 'ready', keepAliveTick );
previewer.bind( 'keep-alive', keepAliveTick );
* Query string data sent with each preview request.
* Refresh the preview seamlessly.
var previewer = this, onSettingChange;
// Display loading indicator.
previewer.send( 'loading-initiated' );
previewer.loading = new api.PreviewFrame({
previewUrl: previewer.previewUrl(),
query: previewer.query( { excludeCustomizedSaved: true } ) || {},
container: previewer.container
previewer.settingsModifiedWhileLoading = {};
onSettingChange = function( setting ) {
previewer.settingsModifiedWhileLoading[ setting.id ] = true;
api.bind( 'change', onSettingChange );
previewer.loading.always( function() {
api.unbind( 'change', onSettingChange );
previewer.loading.done( function( readyData ) {
var loadingFrame = this, onceSynced;
previewer.preview = loadingFrame;
previewer.targetWindow( loadingFrame.targetWindow() );
previewer.channel( loadingFrame.channel() );
onceSynced = function() {
loadingFrame.unbind( 'synced', onceSynced );
if ( previewer._previousPreview ) {
previewer._previousPreview.destroy();
previewer._previousPreview = previewer.preview;
previewer.deferred.active.resolve();
delete previewer.loading;
loadingFrame.bind( 'synced', onceSynced );
// This event will be received directly by the previewer in normal navigation; this is only needed for seamless refresh.
previewer.trigger( 'ready', readyData );
previewer.loading.fail( function( reason ) {
previewer.send( 'loading-failed' );
if ( 'logged out' === reason ) {
if ( previewer.preview ) {
previewer.preview.destroy();
delete previewer.preview;
previewer.login().done( previewer.refresh );
if ( 'cheatin' === reason ) {
deferred, messenger, iframe;
this._login = deferred.promise();
messenger = new api.Messenger({
url: api.settings.url.login
iframe = $( '<iframe />', { 'src': api.settings.url.login, 'title': api.l10n.loginIframeTitle } ).appendTo( this.container );
messenger.targetWindow( iframe[0].contentWindow );
messenger.bind( 'login', function () {
var refreshNonces = previewer.refreshNonces();
refreshNonces.always( function() {
refreshNonces.done( function() {
refreshNonces.fail( function() {
$( document.body ).empty().addClass( 'cheatin' ).append(
'<h1>' + api.l10n.notAllowedHeading + '</h1>' +
'<p>' + api.l10n.notAllowed + '</p>'
refreshNonces: function() {
var request, deferred = $.Deferred();
request = wp.ajax.post( 'customize_refresh_nonces', {
customize_theme: api.settings.theme.stylesheet
request.done( function( response ) {
api.trigger( 'nonce-refresh', response );
request.fail( function() {
api.settingConstructor = {};
api.controlConstructor = {
upload: api.UploadControl,
cropped_image: api.CroppedImageControl,
site_icon: api.SiteIconControl,
header: api.HeaderControl,
background: api.BackgroundControl,
background_position: api.BackgroundPositionControl,
date_time: api.DateTimeControl,
code_editor: api.CodeEditorControl
api.sectionConstructor = {
themes: api.ThemesSection,
* Handle setting_validities in an error response for the customize-save request.
* Add notifications to the settings and focus on the first control that has an invalid setting.
* @alias wp.customize._handleSettingValidities
* @param {Object} args.settingValidities
* @param {boolean} [args.focusInvalidControl=false]
api._handleSettingValidities = function handleSettingValidities( args ) {
var invalidSettingControls, invalidSettings = [], wasFocused = false;
// Find the controls that correspond to each invalid setting.
_.each( args.settingValidities, function( validity, settingId ) {
var setting = api( settingId );
// Add notifications for invalidities.
if ( _.isObject( validity ) ) {
_.each( validity, function( params, code ) {
var notification, existingNotification, needsReplacement = false;
notification = new api.Notification( code, _.extend( { fromServer: true }, params ) );
// Remove existing notification if already exists for code but differs in parameters.
existingNotification = setting.notifications( notification.code );
if ( existingNotification ) {
needsReplacement = notification.type !== existingNotification.type || notification.message !== existingNotification.message || ! _.isEqual( notification.data, existingNotification.data );
if ( needsReplacement ) {
setting.notifications.remove( code );
if ( ! setting.notifications.has( notification.code ) ) {
setting.notifications.add( notification );
invalidSettings.push( setting.id );
// Remove notification errors that are no longer valid.
setting.notifications.each( function( notification ) {
if ( notification.fromServer && 'error' === notification.type && ( true === validity || ! validity[ notification.code ] ) ) {
setting.notifications.remove( notification.code );
if ( args.focusInvalidControl ) {
invalidSettingControls = api.findControlsForSettings( invalidSettings );
// Focus on the first control that is inside of an expanded section (one that is visible).
_( _.values( invalidSettingControls ) ).find( function( controls ) {
return _( controls ).find( function( control ) {
var isExpanded = control.section() && api.section.has( control.section() ) && api.section( control.section() ).expanded();
if ( isExpanded && control.expanded ) {
isExpanded = control.expanded();