diff --git a/src/wp-admin/js/customize-controls.js b/src/wp-admin/js/customize-controls.js index 93b589999f..3c4bf05569 100644 --- a/src/wp-admin/js/customize-controls.js +++ b/src/wp-admin/js/customize-controls.js @@ -2801,11 +2801,24 @@ * @returns {void} */ attachEvents: function() { - var panel = this; + var panel = this, toggleDisabledNotification; // Attach regular panel events. api.Panel.prototype.attachEvents.apply( panel ); + toggleDisabledNotification = function() { + if ( 'publish' === api.state( 'selectedChangesetStatus' ).get() ) { + panel.notifications.remove( 'theme_switch_unavailable' ); + } else { + panel.notifications.add( new api.Notification( 'theme_switch_unavailable', { + message: api.l10n.themePreviewUnavailable, + type: 'warning' + } ) ); + } + }; + toggleDisabledNotification(); + api.state( 'selectedChangesetStatus' ).bind( toggleDisabledNotification ); + // Collapse panel to customize the current theme. panel.contentContainer.on( 'click', '.customize-theme', function() { panel.collapse(); @@ -2887,18 +2900,32 @@ * * @since 4.9.0 * - * @returns {void} + * @param {jQuery.Event} event - Event. + * @returns {jQuery.promise} Promise. */ installTheme: function( event ) { - var panel = this, preview = false, slug = $( event.target ).data( 'slug' ); + var panel = this, preview, onInstallSuccess, slug = $( event.target ).data( 'slug' ), deferred = $.Deferred(), request; + preview = $( event.target ).hasClass( 'preview' ); + // Prevent loading a non-active theme preview when there is a drafted/scheduled changeset. + if ( 'publish' !== api.state( 'selectedChangesetStatus' ).get() && slug !== api.settings.theme.stylesheet ) { + deferred.reject({ + errorCode: 'theme_switch_unavailable' + }); + return deferred.promise(); + } + + // Theme is already being installed. if ( _.contains( panel.installingThemes, slug ) ) { - return; // Theme is already being installed. + deferred.reject({ + errorCode: 'theme_already_installing' + }); + return deferred.promise(); } wp.updates.maybeRequestFilesystemCredentials( event ); - $( document ).one( 'wp-theme-install-success', function( event, response ) { + onInstallSuccess = function( response ) { var theme = false, themeControl; if ( preview ) { api.notifications.remove( 'theme_installing' ); @@ -2915,6 +2942,7 @@ // Don't add the same theme more than once. if ( ! theme || api.control.has( 'installed_theme_' + theme.id ) ) { + deferred.resolve( response ); return; } @@ -2939,23 +2967,29 @@ } }); } - } ); + deferred.resolve( response ); + }; - panel.installingThemes.push( $( event.target ).data( 'slug' ) ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. - wp.updates.installTheme( { + panel.installingThemes.push( slug ); // Note: we don't remove elements from installingThemes, since they shouldn't be installed again. + request = wp.updates.installTheme( { slug: slug } ); // Also preview the theme as the event is triggered on Install & Preview. - if ( $( event.target ).hasClass( 'preview' ) ) { - preview = true; - + if ( preview ) { api.notifications.add( new api.OverlayNotification( 'theme_installing', { message: api.l10n.themeDownloading, type: 'info', loading: true } ) ); } + + request.done( onInstallSuccess ); + request.fail( function() { + api.notifications.remove( 'theme_installing' ); + } ); + + return deferred.promise(); }, /** @@ -2969,6 +3003,11 @@ loadThemePreview: function( themeId ) { var deferred = $.Deferred(), onceProcessingComplete, urlParser, queryParams; + // Prevent loading a non-active theme preview when there is a drafted/scheduled changeset. + if ( 'publish' !== api.state( 'selectedChangesetStatus' ).get() && themeId !== api.settings.theme.stylesheet ) { + return deferred.reject().promise(); + } + urlParser = document.createElement( 'a' ); urlParser.href = location.href; queryParams = _.extend( @@ -4741,7 +4780,17 @@ * @since 4.2.0 */ ready: function() { - var control = this; + var control = this, disableSwitchButtons, updateButtons; + + disableSwitchButtons = function() { + return 'publish' !== api.state( 'selectedChangesetStatus' ).get() && control.params.theme.id !== api.settings.theme.stylesheet; + }; + updateButtons = function() { + control.container.find( 'button' ).toggleClass( 'disabled', disableSwitchButtons() ); + }; + + api.state( 'selectedChangesetStatus' ).bind( updateButtons ); + updateButtons(); control.container.on( 'touchmove', '.theme', function() { control.touchDrag = true; @@ -4749,6 +4798,7 @@ // Bind details view trigger. control.container.on( 'click keydown touchend', '.theme', function( event ) { + var section; if ( api.utils.isKeydownButNotEnterEvent( event ) ) { return; } @@ -4764,7 +4814,10 @@ } event.preventDefault(); // Keep this AFTER the key filter above - api.section( control.section() ).showDetails( control.params.theme ); + section = api.section( control.section() ); + section.showDetails( control.params.theme, function() { + section.overlay.find( '.theme-actions button' ).toggleClass( 'disabled', disableSwitchButtons() ); + } ); }); control.container.on( 'render-screenshot', function() { @@ -7339,24 +7392,6 @@ history.replaceState( {}, document.title, urlParser.href ); }; - // Deactivate themes panel if changeset status is not auto-draft. - api.panel( 'themes', function( themesPanel ) { - var isPanelActive, updatePanelActive; - - isPanelActive = function() { - return 'publish' === selectedChangesetStatus.get() && ( ! changesetStatus() || 'auto-draft' === changesetStatus() ); - }; - themesPanel.active.validate = isPanelActive; - - updatePanelActive = function() { - themesPanel.active.set( isPanelActive() ); - }; - - updatePanelActive(); - changesetStatus.bind( updatePanelActive ); - selectedChangesetStatus.bind( updatePanelActive ); - } ); - // Show changeset UUID in URL when in branching mode and there is a saved changeset. if ( api.settings.changeset.branching ) { changesetStatus.bind( function( newStatus ) { diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php index 955d59eee6..cea28c620f 100644 --- a/src/wp-includes/class-wp-customize-manager.php +++ b/src/wp-includes/class-wp-customize-manager.php @@ -607,7 +607,7 @@ final class WP_Customize_Manager { if ( empty( $this->_changeset_uuid ) ) { $changeset_uuid = null; - if ( ! $this->branching() ) { + if ( ! $this->branching() && $this->is_theme_active() ) { $unpublished_changeset_posts = $this->get_changeset_posts( array( 'post_status' => array_diff( get_post_stati(), array( 'auto-draft', 'publish', 'trash', 'inherit', 'private' ) ), 'exclude_restore_dismissed' => false, diff --git a/src/wp-includes/customize/class-wp-customize-themes-panel.php b/src/wp-includes/customize/class-wp-customize-themes-panel.php index 1db08db73e..6f346d13d1 100644 --- a/src/wp-includes/customize/class-wp-customize-themes-panel.php +++ b/src/wp-includes/customize/class-wp-customize-themes-panel.php @@ -88,6 +88,8 @@ class WP_Customize_Themes_Panel extends WP_Customize_Panel { <# } #> + +
  • diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index c17c46ea4d..d3af4c0a44 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -580,7 +580,7 @@ function wp_default_scripts( &$scripts ) { 'trashConfirm' => __( 'Are you sure you’d like to discard your unpublished changes?' ), /* translators: %s: URL to the Customizer to load the autosaved version */ 'autosaveNotice' => __( 'There is a more recent autosave of your changes than the one you are previewing. Restore the autosave' ), - 'videoHeaderNotice' => __( 'This theme doesn\'t support video headers on this page. Navigate to the front page or another page that supports video headers.' ), + 'videoHeaderNotice' => __( 'This theme doesn’t support video headers on this page. Navigate to the front page or another page that supports video headers.' ), // Used for overriding the file types allowed in plupload. 'allowedFiles' => __( 'Allowed Files' ), 'customCssError' => wp_array_slice_assoc( @@ -595,6 +595,7 @@ function wp_default_scripts( &$scripts ) { array( 'singular', 'plural' ) ), 'scheduleDescription' => __( 'Schedule your customization changes to publish ("go live") at a future date.' ), + 'themePreviewUnavailable' => __( 'Sorry, you can’t preview new themes when you have changes scheduled or saved as a draft. Please publish your changes, or wait until they publish to preview new themes.' ), ) ); $scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 ); diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php index 1c1814b6a2..6938b7c3bb 100644 --- a/tests/phpunit/tests/customize/manager.php +++ b/tests/phpunit/tests/customize/manager.php @@ -197,6 +197,15 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase { ) ); $this->assertNotContains( $wp_customize->changeset_uuid(), array( $uuid1, $uuid2 ) ); $this->assertEmpty( $wp_customize->changeset_post_id() ); + + // Make sure existing changeset is not autoloaded in the case of previewing a theme switch. + switch_theme( 'twentyseventeen' ); + $wp_customize = new WP_Customize_Manager( array( + 'changeset_uuid' => false, // Cause UUID to be deferred. + 'branching' => false, + 'theme' => 'twentyfifteen', + ) ); + $this->assertEmpty( $wp_customize->changeset_post_id() ); } /**