WordPress.org

Make WordPress Core

Ticket #31303: 31303.2.diff

File 31303.2.diff, 31.0 KB (added by celloexpressions, 7 years ago)

Address issues that required core patches and couldn't be implemented in the plugin.

  • src/wp-admin/css/customize-controls.css

     
    835835        float: right;
    836836}
    837837
     838/**
     839 * Themes
     840 */
     841@-webkit-keyframes customize-reload {
     842        0%   { opacity: 0; }
     843        100% { opacity: 1; }
     844}
    838845
     846@-moz-keyframes customize-reload {
     847        0%   { opacity: 0; }
     848        100% { opacity: 1; }
     849}
     850
     851@keyframes customize-reload {
     852        0%   { opacity: 0; }
     853        100% { opacity: 1; }
     854}
     855
     856/* #customize-container is reused from customize-loader.js, hence the naming. */
     857.wp-customizer .customize-loading #customize-container {
     858        display: block;
     859        -webkit-animation: customize-reload .75s; /* Can't use `transition` because `display` changes here. */
     860        -moz-animation: customize-reload .75s;
     861        animation: customize-reload .75s;
     862}
     863
     864.customize-themes-panel {
     865        display: none;
     866        padding: 0 8px;
     867        background: #f1f1f1;
     868        box-sizing: border-box;
     869        -webkit-box-sizing: border-box;
     870        -moz-box-sizing: border-box;
     871}
     872
     873.control-section.open .customize-themes-panel {
     874        display: block;
     875}
     876
     877#customize-theme-controls .customize-themes-panel .accordion-section-content {
     878        background: transparent;
     879        display: block;
     880}
     881
     882.customize-control.customize-control-theme {
     883        margin-bottom: 8px;
     884}
     885
     886.wp-customizer .theme-browser .themes {
     887        padding-bottom: 8px;
     888}
     889
     890.wp-customizer .theme-browser .theme {
     891        margin: 0;
     892        width: 100%;
     893}
     894
     895.wp-customizer .theme-browser .theme .theme-actions {
     896        -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
     897        opacity: 1;
     898}
     899
     900#customize-controls h3.theme-name {
     901        font-size: 15px;
     902}
     903
     904.wp-customizer .theme-browser .theme.active .theme-name {
     905        padding-right: 15px;
     906}
     907
     908.wp-customizer #themes-filter {
     909        width: 100%;
     910}
     911
     912/* Panel-like behavior */
     913#accordion-section-themes .accordion-section-title:after {
     914        content: "\f148";
     915}
     916
     917.rtl #accordion-section-themes .accordion-section-title:after {
     918        -webkit-transform: rotate(180deg);
     919        -ms-transform: rotate(180deg);
     920        transform: rotate(180deg);
     921}
     922
     923#customize-theme-controls .control-section.current-panel > h3.accordion-section-title {
     924        left: 0;
     925}
     926
     927.customize-themes-panel.control-panel-content {
     928        position: absolute;
     929        left: -100%;
     930        top: 0;
     931        width: 100%;
     932        border-top: 1px solid #ddd;
     933}
     934
     935.in-themes-panel #customize-info,
     936.in-themes-panel #customize-theme-controls > ul > .accordion-section {
     937        left: 100%;
     938}
     939
     940.themes-panel-back:before {
     941        top: 13px;
     942        left: 14px;
     943}
     944
     945.in-themes-panel .themes-panel-back {
     946        left: 0;
     947}
     948
     949.in-sub-panel .themes-panel-back {
     950        display: none;
     951}
     952
     953.control-panel-back.themes-panel-back:before {
     954        content: "\f345";
     955}
     956
     957.rtl .control-panel-back.themes-panel-back:before {
     958        content: "\f341";
     959}
     960
     961/* Details View */
     962.wp-customizer .theme-overlay {
     963        display: none;
     964}
     965
     966.wp-customizer.modal-open .theme-overlay {
     967        position: fixed;
     968        left: 0;
     969        top: 0;
     970        right: 0;
     971        bottom: 0;
     972        z-index: 109;
     973}
     974
     975.wp-customizer .theme-overlay .theme-backdrop {
     976        background: rgba( 238, 238, 238, 0.75 );
     977        position: fixed;
     978        z-index: 110;
     979}
     980
     981.wp-customizer .theme-overlay .theme-wrap {
     982        left: 90px;
     983        right: 90px;
     984        top: 45px;
     985        bottom: 45px;
     986        z-index: 120;
     987        max-width: 1740px; /* To ensure that theme screenshots are not displayed larger than 880px wide. */
     988}
     989
     990.wp-customizer .theme-overlay .theme-actions {
     991        text-align: right; /* Because there's only one action, match the pattern of media modals and right-align the action. */
     992}
     993
     994.modal-open .in-themes-panel #customize-controls .wp-full-overlay-sidebar-content {
     995        overflow: visible; /* Prevent the top-level Customizer controls from becoming visible when elements on the right of the details modal are focused. */
     996}
     997
     998/* Small Screens */
     999@media (max-width:850px), (max-height:472px) {
     1000        .wp-customizer .theme-overlay .theme-wrap {
     1001                left: 0;
     1002                right: 0;
     1003                top: 0;
     1004                bottom: 0;
     1005        }       
     1006}
     1007
     1008
    8391009/** Handle cheaters. */
    8401010body.cheatin {
    8411011        font-size: medium;
  • src/wp-admin/customize.php

     
    136136                        <span class="control-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></span>
    137137                </div>
    138138
    139                 <?php
    140                         $screenshot = $wp_customize->theme()->get_screenshot();
    141                         $cannot_expand = ! ( $wp_customize->is_theme_active() || $screenshot || $wp_customize->theme()->get('Description') );
    142                 ?>
    143 
    144139                <div id="widgets-right"><!-- For Widget Customizer, many widgets try to look for instances under div#widgets-right, so we have to add that ID to a container div in the Customizer for compat -->
    145140                <div class="wp-full-overlay-sidebar-content" tabindex="-1">
    146                         <div id="customize-info" class="accordion-section <?php if ( $cannot_expand ) echo ' cannot-expand'; ?>">
     141                        <div id="customize-info" class="accordion-section">
    147142                                <div class="accordion-section-title" aria-label="<?php esc_attr_e( 'Customizer Options' ); ?>" tabindex="0">
    148143                                        <span class="preview-notice"><?php
    149                                                 if ( ! $wp_customize->is_theme_active() ) {
    150                                                         /* translators: %s is the theme name in the Customize/Live Preview pane */
    151                                                         echo sprintf( __( 'You are previewing %s' ), '<strong class="theme-name">' . $wp_customize->theme()->display('Name') . '</strong>' );
    152                                                 } else {
    153                                                         /* translators: %s is the site/panel title in the Customize pane */
    154                                                         echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
    155                                                 }
     144                                                echo sprintf( __( 'You are customizing %s' ), '<strong class="theme-name site-title">' . get_bloginfo( 'name' ) . '</strong>' );
    156145                                        ?></span>
    157146                                </div>
    158                                 <?php if ( ! $cannot_expand ) : ?>
    159                                 <div class="accordion-section-content">
    160                                         <?php if ( ! $wp_customize->is_theme_active() ) :
    161                                                 if ( $screenshot ) : ?>
    162                                                         <img class="theme-screenshot" src="<?php echo esc_url( $screenshot ); ?>" />
    163                                                 <?php endif; ?>
    164 
    165                                                 <?php if ( $wp_customize->theme()->get('Description') ): ?>
    166                                                         <div class="theme-description"><?php echo $wp_customize->theme()->display('Description'); ?></div>
    167                                                 <?php endif;
    168                                         else:
    169                                                 echo __( 'The Customizer allows you to preview changes to your site before publishing them. You can also navigate to different pages on your site to preview them.' );
    170                                         endif; ?>
    171                                 </div>
    172                                 <?php endif; ?>
     147                                <div class="accordion-section-content"><?php
     148                                        echo __( 'The Customizer allows you to preview changes to your site before publishing them. You can also navigate to different pages on your site to preview them.' );
     149                                ?></div>
    173150                        </div>
    174151
    175152                        <div id="customize-theme-controls">
     
    246223                'url'      => array(
    247224                        'preview'       => esc_url_raw( $url ? $url : home_url( '/' ) ),
    248225                        'parent'        => esc_url_raw( admin_url() ),
    249                         'activated'     => esc_url_raw( admin_url( 'themes.php?activated=true&previewed' ) ),
     226                        'activated'     => esc_url_raw( home_url( '/' ) ),
    250227                        'ajax'          => esc_url_raw( admin_url( 'admin-ajax.php', 'relative' ) ),
    251228                        'allowed'       => array_map( 'esc_url_raw', $allowed_urls ),
    252229                        'isCrossDomain' => $cross_domain,
  • src/wp-admin/includes/theme.php

     
    486486        $prepared_themes = apply_filters( 'wp_prepare_themes_for_js', $prepared_themes );
    487487        return array_values( $prepared_themes );
    488488}
     489
     490/**
     491 * Print JS templates for the theme-browsing UI in the Customizer.
     492 *
     493 * @since 4.2.0
     494 */
     495function customize_themes_print_templates() {
     496        ?>
     497        <script type="text/html" id="tmpl-customize-themes-details-view">
     498                <div class="theme-backdrop"></div>
     499                <div class="theme-wrap">
     500                        <div class="theme-header">
     501                                <button type="button" class="left dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show previous theme' ); ?></span></button>
     502                                <button type="button" class="right dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Show next theme' ); ?></span></button>
     503                                <button type="button" class="close dashicons dashicons-no"><span class="screen-reader-text"><?php _e( 'Close details dialog' ); ?></span></button>
     504                        </div>
     505                        <div class="theme-about">
     506                                <div class="theme-screenshots">
     507                                <# if ( data.screenshot[0] ) { #>
     508                                        <div class="screenshot"><img src="{{ data.screenshot[0] }}" alt="" /></div>
     509                                <# } else { #>
     510                                        <div class="screenshot blank"></div>
     511                                <# } #>
     512                                </div>
     513
     514                                <div class="theme-info">
     515                                        <# if ( data.active ) { #>
     516                                                <span class="current-label"><?php _e( 'Current Theme' ); ?></span>
     517                                        <# } #>
     518                                        <h3 class="theme-name">{{{ data.name }}}<span class="theme-version"><?php printf( __( 'Version: %s' ), '{{ data.version }}' ); ?></span></h3>
     519                                        <h4 class="theme-author"><?php printf( __( 'By %s' ), '{{{ data.authorAndUri }}}' ); ?></h4>
     520                                        <p class="theme-description">{{{ data.description }}}</p>
     521
     522                                        <# if ( data.parent ) { #>
     523                                                <p class="parent-theme"><?php printf( __( 'This is a child theme of %s.' ), '<strong>{{{ data.parent }}}</strong>' ); ?></p>
     524                                        <# } #>
     525
     526                                        <# if ( data.tags ) { #>
     527                                                <p class="theme-tags"><span><?php _e( 'Tags:' ); ?></span> {{ data.tags }}</p>
     528                                        <# } #>
     529                                </div>
     530                        </div>
     531
     532                        <div class="theme-actions">
     533                                <# if ( ! data.active ) { #>
     534                                        <div class="inactive-theme">
     535                                                <a href="<?php echo add_query_arg( 'theme', '{{ data.id }}', remove_query_arg( 'theme' ) ); ?>" target="_top" class="button button-primary"><?php _e( 'Live Preview' ); ?></a>
     536                                        </div>
     537                                <# } #>
     538                        </div>
     539                </div>
     540        </script>
     541        <?php
     542}
     543add_action( 'customize_controls_print_footer_scripts', 'customize_themes_print_templates' );
  • src/wp-admin/js/customize-controls.js

     
    521521        });
    522522
    523523        /**
     524         * wp.customize.ThemesSection
     525         *
     526         * Custom section for themes that functions similarly to a backwards panel,
     527         * and also handles the theme-details view rendering and navigation.
     528         *
     529         * @constructor
     530         * @augments wp.customize.Section
     531         * @augments wp.customize.Container
     532         */
     533        api.ThemesSection = api.Section.extend({
     534                currentTheme: '',
     535                overlay: '',
     536                template: '',
     537
     538                /**
     539                 * @since 4.2.0
     540                 */
     541                ready: function () {
     542                        var section = this;
     543                        section.overlay = section.container.find( '.theme-overlay' );
     544                        section.template = wp.template( 'customize-themes-details-view' );
     545
     546                        // Bind global keyboard events.
     547                        $( 'body' ).on( 'keyup', function( event ) {
     548                                if ( ! section.overlay.find( '.theme-wrap' ).is( ':visible' ) ) {
     549                                        return;
     550                                }
     551
     552                                // Pressing the right arrow key fires a theme:next event
     553                                if ( 39 === event.keyCode ) {
     554                                        section.nextTheme();
     555                                }
     556
     557                                // Pressing the left arrow key fires a theme:previous event
     558                                if ( 37 === event.keyCode ) {
     559                                        section.previousTheme();
     560                                }
     561
     562                                // Pressing the escape key fires a theme:collapse event
     563                                if ( 27 === event.keyCode ) {
     564                                        section.closeDetails();
     565                                }
     566                        });
     567                },
     568
     569                /**
     570                 * @since 4.2.0
     571                 */
     572                attachEvents: function () {
     573                        var meta, section = this;
     574
     575                        // Expand/Collapse section/panel.
     576                        section.container.find( '.accordion-section-title' ).on( 'click keydown', function( event ) {
     577                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     578                                        return;
     579                                }
     580                                event.preventDefault(); // Keep this AFTER the key filter above
     581
     582                                if ( section.expanded() ) {
     583                                        section.collapse();
     584                                } else {
     585                                        section.expand();
     586                                }
     587                        });
     588
     589                        section.container.find( '.themes-panel-back' ).on( 'click keydown', function( event ) {
     590                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     591                                        return;
     592                                }
     593
     594                                event.preventDefault(); // Keep this AFTER the key filter above
     595
     596                                section.collapse();
     597                        });
     598
     599                        // Theme navigation in details view.
     600                        section.container.on( 'click keydown', '.left', function( event ) {
     601                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     602                                        return;
     603                                }
     604
     605                                event.preventDefault(); // Keep this AFTER the key filter above
     606
     607                                section.previousTheme();
     608                        });
     609
     610                        section.container.on( 'click keydown', '.right', function( event ) {
     611                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     612                                        return;
     613                                }
     614
     615                                event.preventDefault(); // Keep this AFTER the key filter above
     616
     617                                section.nextTheme();
     618                        });
     619
     620                        section.container.on( 'click keydown', '.theme-backdrop, .close', function( event ) {
     621                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     622                                        return;
     623                                }
     624
     625                                event.preventDefault(); // Keep this AFTER the key filter above
     626
     627                                section.closeDetails();
     628                        });
     629
     630                        section.container.on( 'click keydown', '.theme-actions .button', function( event ) {
     631                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     632                                        return;
     633                                }
     634
     635                                $( '.wp-full-overlay' ).addClass( 'customize-loading' );
     636                        });
     637
     638                        section.container.on( 'input', '#themes-filter', function( event ) {
     639                                var term = event.currentTarget.value.toLowerCase().trim().replace( '-', ' ' ),
     640                                        controls = section.controls();
     641                                controls.pop(); // Remove the last control (the add-new control).
     642                                _.each( controls, function( control ) {
     643                                        control.filter( term );
     644                                });
     645                                // Update theme count. Note that the add-theme tile is a div.customize-control.
     646                                count = section.container.find( 'li.customize-control:visible' ).length;
     647                                section.container.find( '.theme-count' ).text( count );
     648                        });
     649                },
     650
     651                /**
     652                 * Update UI to reflect expanded state
     653                 *
     654                 * @since 4.2.0
     655                 *
     656                 * @param {Boolean}  expanded
     657                 * @param {Object}   args
     658                 * @param {Boolean}  args.unchanged
     659                 * @param {Callback} args.completeCallback
     660                 */
     661                onChangeExpanded: function ( expanded, args ) {
     662
     663                        // Immediately call the complete callback if there were no changes
     664                        if ( args.unchanged ) {
     665                                if ( args.completeCallback ) {
     666                                        args.completeCallback();
     667                                }
     668                                return;
     669                        }
     670
     671                        // Note: there is a second argument 'args' passed
     672                        var position, scroll,
     673                                panel = this,
     674                                section = panel.container.closest( '.accordion-section' ),
     675                                overlay = section.closest( '.wp-full-overlay' ),
     676                                container = section.closest( '.accordion-container' ),
     677                                siblings = container.find( '.open' ),
     678                                topPanel = overlay.find( '#customize-theme-controls > ul > .accordion-section > .accordion-section-title' ).add( '#customize-info > .accordion-section-title' ),
     679                                backBtn = overlay.find( '.themes-panel-back' ),
     680                                panelTitle = section.find( '.accordion-section-title' ).first(),
     681                                content = section.find( '.control-panel-content' );
     682
     683                        if ( expanded ) {
     684
     685                                // Collapse any sibling sections/panels
     686                                api.section.each( function ( otherSection ) {
     687                                        if ( otherSection !== panel ) {
     688                                                otherSection.collapse( { duration: args.duration } );
     689                                        }
     690                                });
     691                                api.panel.each( function ( otherPanel ) {
     692                                        if ( panel !== otherPanel ) {
     693                                                otherPanel.collapse( { duration: 0 } );
     694                                        }
     695                                });
     696
     697                                content.show( 0, function() {
     698                                        position = content.offset().top;
     699                                        scroll = container.scrollTop();
     700                                        content.css( 'margin-top', ( 45 - position - scroll ) );
     701                                        section.addClass( 'current-panel' );
     702                                        overlay.addClass( 'in-themes-panel' );
     703                                        container.scrollTop( 0 );
     704                                        if ( args.completeCallback ) {
     705                                                args.completeCallback();
     706                                        }
     707                                } );
     708                                topPanel.attr( 'tabindex', '-1' );
     709                                backBtn.attr( 'tabindex', '0' );
     710                                backBtn.focus();
     711                        } else {
     712                                siblings.removeClass( 'open' );
     713                                section.removeClass( 'current-panel' );
     714                                overlay.removeClass( 'in-themes-panel' );
     715                                content.delay( 180 ).hide( 0, function() {
     716                                        content.css( 'margin-top', 'inherit' ); // Reset
     717                                        if ( args.completeCallback ) {
     718                                                args.completeCallback();
     719                                        }
     720                                } );
     721                                topPanel.attr( 'tabindex', '0' );
     722                                backBtn.attr( 'tabindex', '-1' );
     723                                panelTitle.focus();
     724                                container.scrollTop( 0 );
     725                        }
     726                },
     727
     728                /**
     729                 * Advance the modal to the next theme.
     730                 *
     731                 * @since 4.2.0
     732                 */
     733                nextTheme: function () {
     734                        var section = this;
     735                        if ( section.getNextTheme() ) {
     736                                section.showDetails( section.getNextTheme(), function() {
     737                                        section.overlay.find( '.right' ).focus();
     738                                } );
     739                        }
     740                },
     741
     742                /**
     743                 * Get the next theme model.
     744                 *
     745                 * @since 4.2.0
     746                 */
     747                getNextTheme: function () {
     748                        var control, next;
     749                        control = api.control( 'theme_' + this.currentTheme );
     750                        next = control.container.next( 'li.customize-control-theme' );
     751                        if ( ! next.length ) {
     752                                return false;
     753                        }
     754                        next = next[0].id.replace( 'customize-control-', '' );
     755                        control = api.control( next );
     756
     757                        return control.params.theme;
     758                },
     759
     760                /**
     761                 * Advance the modal to the previous theme.
     762                 *
     763                 * @since 4.2.0
     764                 */
     765                previousTheme: function () {
     766                        var section = this;
     767                        if ( section.getPreviousTheme() ) {
     768                                section.showDetails( section.getPreviousTheme(), function() {
     769                                        section.overlay.find( '.left' ).focus();
     770                                } );
     771                        }
     772                },
     773
     774                /**
     775                 * Get the previous theme model.
     776                 *
     777                 * @since 4.2.0
     778                 */
     779                getPreviousTheme: function () {
     780                        var control, previous;
     781                        control = api.control( 'theme_' + this.currentTheme );
     782                        previous = control.container.prev( 'li.customize-control-theme' );
     783                        if ( ! previous.length ) {
     784                                return false;
     785                        }
     786                        previous = previous[0].id.replace( 'customize-control-', '' );
     787                        control = api.control( previous );
     788
     789                        return control.params.theme;
     790                },
     791
     792                /**
     793                 * Disable buttons when we're viewing the first or last theme.
     794                 *
     795                 * @since 4.2.0
     796                 */
     797                updateLimits: function () {
     798                        if ( ! this.getNextTheme() ) {
     799                                this.overlay.find( '.right' ).addClass( 'disabled' );
     800                        }
     801                        if ( ! this.getPreviousTheme() ) {
     802                                this.overlay.find( '.left' ).addClass( 'disabled' );
     803                        }
     804                },
     805
     806                /**
     807                 * Render & show the theme details for a given theme model.
     808                 *
     809                 * @since 4.2.0
     810                 *
     811                 * @param {Object}   theme
     812                 */
     813                showDetails: function ( theme, callback ) {
     814                        var section = this;
     815                        callback = callback || function(){};
     816                        section.currentTheme = theme.id;
     817                        section.overlay.html( section.template( theme ) )
     818                                       .fadeIn( 'fast' )
     819                                       .focus();
     820                        $( 'body' ).addClass( 'modal-open' );
     821                        section.containFocus( section.overlay );
     822                        section.updateLimits();
     823                        callback();
     824                },
     825
     826                /**
     827                 * Close the theme details modal.
     828                 *
     829                 * @since 4.2.0
     830                 */
     831                closeDetails: function ( theme ) {
     832                        $( 'body' ).removeClass( 'modal-open' );
     833                        this.overlay.fadeOut( 'fast' );
     834                        api.control( 'theme_' + this.currentTheme ).focus();
     835                },
     836
     837                /**
     838                 * Keep tab focus within the theme details modal.
     839                 *
     840                 * @since 4.2.0
     841                 */
     842                containFocus: function( el ) {
     843                        var tabbables;
     844
     845                        el.on( 'keydown', function( event ) {
     846
     847                                // Return if it's not the tab key
     848                                // When navigating with prev/next focus is already handled
     849                                if ( 9 !== event.keyCode ) {
     850                                        return;
     851                                }
     852
     853                                // uses jQuery UI to get the tabbable elements
     854                                tabbables = $( ':tabbable', el );
     855
     856                                // Keep focus within the overlay
     857                                if ( tabbables.last()[0] === event.target && ! event.shiftKey ) {
     858                                        tabbables.first().focus();
     859                                        return false;
     860                                } else if ( tabbables.first()[0] === event.target && event.shiftKey ) {
     861                                        tabbables.last().focus();
     862                                        return false;
     863                                }
     864                        });
     865                }
     866        });
     867
     868        /**
    524869         * @since 4.1.0
    525870         *
    526871         * @class
     
    14091754
    14101755        });
    14111756
     1757        /**
     1758         * wp.customize.ThemeControl
     1759         *
     1760         * @constructor
     1761         * @augments wp.customize.Control
     1762         * @augments wp.customize.Class
     1763         */
     1764        api.ThemeControl = api.Control.extend({
     1765
     1766                /**
     1767                 * @since 4.2.0
     1768                 */
     1769                ready: function() {
     1770                        var control = this;
     1771
     1772                        // Bind details view trigger.
     1773                        control.container.on( 'click keydown', '.theme', function( event ) {
     1774                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1775                                        return;
     1776                                }
     1777
     1778                                if ( 'button' === event.target.className ) {
     1779                                        return;
     1780                                }
     1781
     1782                                api.section( control.section() ).showDetails( control.params.theme );
     1783                        });
     1784
     1785                        control.container.on( 'click keydown', '.theme-actions .button', function( event ) {
     1786                                if ( api.utils.isKeydownButNotEnterEvent( event ) ) {
     1787                                        return;
     1788                                }
     1789
     1790                                $( '.wp-full-overlay' ).addClass( 'customize-loading' );
     1791                        });
     1792                },
     1793
     1794                /**
     1795                 * Show or hide the theme based on the presence of the term in the title, description, and author.
     1796                 *
     1797                 * @since 4.2.0
     1798                 */
     1799                filter: function( term ) {
     1800                        var control = this,
     1801                            haystack = control.params.theme.name + ' '
     1802                                           + control.params.theme.description + ' '
     1803                                           + control.params.theme.tags + ' '
     1804                                           + control.params.theme.author;
     1805                        haystack = haystack.toLowerCase().replace( '-', ' ' );
     1806                        if ( -1 !== haystack.search( term ) ) {
     1807                                control.activate();
     1808                        } else {
     1809                                control.deactivate();
     1810                        }
     1811                }
     1812        });
     1813
    14121814        // Change objects contained within the main customize object to Settings.
    14131815        api.defaultConstructor = api.Setting;
    14141816
     
    18532255        });
    18542256
    18552257        api.controlConstructor = {
    1856                 color:  api.ColorControl,
    1857                 upload: api.UploadControl,
    1858                 image:  api.ImageControl,
    1859                 header: api.HeaderControl,
    1860                 background: api.BackgroundControl
     2258                color:      api.ColorControl,
     2259                upload:     api.UploadControl,
     2260                image:      api.ImageControl,
     2261                header:     api.HeaderControl,
     2262                background: api.BackgroundControl,
     2263                theme:      api.ThemeControl
    18612264        };
    18622265        api.panelConstructor = {};
    1863         api.sectionConstructor = {};
     2266        api.sectionConstructor = {
     2267                themes: api.ThemesSection
     2268        };
    18642269
    18652270        $( function() {
    18662271                api.settings = window._wpCustomizeSettings;
     
    22732678                // Prompt user with AYS dialog if leaving the Customizer with unsaved changes
    22742679                $( window ).on( 'beforeunload', function () {
    22752680                        if ( ! api.state( 'saved' )() ) {
     2681                                var timeout = setTimeout( function() {
     2682                                        overlay.removeClass( 'customize-loading' );
     2683                                }, 1 );
    22762684                                return api.l10n.saveAlert;
    22772685                        }
    22782686                } );
  • src/wp-includes/admin-bar.php

     
    650650function wp_admin_bar_appearance_menu( $wp_admin_bar ) {
    651651        $wp_admin_bar->add_group( array( 'parent' => 'site-name', 'id' => 'appearance' ) );
    652652
    653         if ( current_user_can( 'switch_themes' ) || current_user_can( 'edit_theme_options' ) )
    654                 $wp_admin_bar->add_menu( array( 'parent' => 'appearance', 'id' => 'themes', 'title' => __('Themes'), 'href' => admin_url('themes.php') ) );
     653        $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
     654        $customize_url = add_query_arg( 'url', urlencode( $current_url ), wp_customize_url() );
    655655
    656         if ( ! current_user_can( 'edit_theme_options' ) )
    657                 return;
     656        if ( current_user_can( 'switch_themes' ) ) {
     657                $wp_admin_bar->add_menu( array(
     658                        'parent' => 'appearance',
     659                        'id'     => 'themes',
     660                        'title'  => __( 'Themes' ),
     661                        'href'   => admin_url( 'themes.php' ),
     662                        'meta'   => array(
     663                                'class' => 'hide-if-customize',
     664                        ),
     665                ) );
    658666
    659         $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
    660         $customize_url = add_query_arg( 'url', urlencode( $current_url ), wp_customize_url() );
     667                if ( current_user_can( 'customize' ) ) {
     668                        $wp_admin_bar->add_menu( array(
     669                                'parent' => 'appearance',
     670                                'id'     => 'customize-themes',
     671                                'title'  => __( 'Themes' ),
     672                                'href'   => add_query_arg( urlencode( 'autofocus[section]' ), 'themes', $customize_url ), // urlencode() needed due to #16859
     673                                'meta'   => array(
     674                                        'class' => 'hide-if-no-customize',
     675                                ),
     676                        ) );
     677                }
     678        }
     679
    661680        if ( current_user_can( 'customize' ) ) {
    662681                $wp_admin_bar->add_menu( array(
    663682                        'parent' => 'appearance',
     
    671690                add_action( 'wp_before_admin_bar_render', 'wp_customize_support_script' );
    672691        }
    673692
     693        if ( ! current_user_can( 'edit_theme_options' ) ) {
     694                return;
     695        }
     696
    674697        if ( current_theme_supports( 'widgets' )  ) {
    675698                $wp_admin_bar->add_menu( array(
    676699                        'parent' => 'appearance',
  • src/wp-includes/class-wp-customize-control.php

     
    11011101}
    11021102
    11031103/**
     1104 * Customize Theme Control Class
     1105 *
     1106 * @package WordPress
     1107 * @subpackage Customize
     1108 * @since 4.2.0
     1109 */
     1110class WP_Customize_Theme_Control extends WP_Customize_Control {
     1111
     1112        public $type = 'theme';
     1113        public $theme;
     1114
     1115        /**
     1116         * Refresh the parameters passed to the JavaScript via JSON.
     1117         *
     1118         * @since 4.2.0
     1119         * @uses WP_Customize_Control::to_json()
     1120         */
     1121        public function to_json() {
     1122                parent::to_json();
     1123                $this->json['theme'] = $this->theme;
     1124        }
     1125
     1126        /**
     1127         * Don't render the control content from PHP, as it's rendered via JS on load.
     1128         *
     1129         * @since 4.2.0
     1130         */
     1131        public function render_content() {}
     1132
     1133        /**
     1134         * Render a JS template for theme display.
     1135         *
     1136         * @since 4.2.0
     1137         */
     1138        public function content_template() {
     1139        ?>
     1140                <div class="theme<# if ( data.theme.active ) { #> active<# } #>" tabindex="0" aria-describedby="{{ data.theme.id }}-action {{ data.theme.id }}-name">
     1141                        <# if ( data.theme.screenshot[0] ) { #>
     1142                                <div class="theme-screenshot">
     1143                                        <img src="{{ data.theme.screenshot[0] }}" alt="" />
     1144                                </div>
     1145                        <# } else { #>
     1146                                <div class="theme-screenshot blank"></div>
     1147                        <# } #>
     1148                        <span class="more-details" id="{{ data.theme.id }}-action"><?php _e( 'Theme Details' ); ?></span>
     1149                        <div class="theme-author"><?php printf( __( 'By %s' ), '{{ data.theme.author }}' ); ?></div>
     1150
     1151                        <# if ( data.theme.active ) { #>
     1152                                <h3 class="theme-name" id="{{ data.theme.id }}-name"><span><?php _ex( 'Previewing:', 'theme' ); ?></span> {{ data.theme.name }}</h3>
     1153                        <# } else { #>
     1154                                <h3 class="theme-name" id="{{ data.theme.id }}-name">{{ data.theme.name }}</h3>
     1155                        <# } #>
     1156
     1157                        <# if ( ! data.theme.active ) { #>
     1158                                <div class="theme-actions">
     1159                                        <a class="button" href="<?php echo add_query_arg( 'theme', '{{ data.theme.id }}', remove_query_arg( 'theme' ) ); ?>" target="_top"><?php _e( 'Live Preview' ); ?></a>
     1160                                </div>
     1161                        <# } #>
     1162                </div>
     1163        <?php
     1164        }
     1165}
     1166
     1167/**
     1168 * Customize New Theme Control Class
     1169 *
     1170 * @package WordPress
     1171 * @subpackage Customize
     1172 * @since 4.2.0
     1173 */
     1174class WP_Customize_New_Theme_Control extends WP_Customize_Control {
     1175
     1176        /**
     1177         * Render the new control.
     1178         *
     1179         * @since 4.2.0
     1180         */
     1181        public function render() {
     1182                if ( is_multisite() || ! current_user_can( 'install_themes') ) {
     1183                        return;
     1184                }
     1185                ?>
     1186                <div class="theme add-new-theme">
     1187                        <a href="<?php echo admin_url( 'theme-install.php' ); ?>" target="_top">
     1188                                <div class="theme-screenshot">
     1189                                        <span></span>
     1190                                </div>
     1191                                <h3 class="theme-name"><?php _e( 'Add New Theme' ); ?></h3>
     1192                        </a>
     1193                </div>
     1194                <?php
     1195        }
     1196}
     1197
     1198/**
    11041199 * Widget Area Customize Control Class
    11051200 *
    11061201 * @since 3.9.0
  • src/wp-includes/class-wp-customize-manager.php

     
    11101110                $this->register_control_type( 'WP_Customize_Upload_Control' );
    11111111                $this->register_control_type( 'WP_Customize_Image_Control' );
    11121112                $this->register_control_type( 'WP_Customize_Background_Image_Control' );
     1113                $this->register_control_type( 'WP_Customize_Theme_Control' );
    11131114
     1115                /* Themes */
     1116
     1117                $this->add_section( new WP_Customize_Themes_Section( $this, 'themes', array(
     1118                        'title' => sprintf( __( 'Theme: %s' ), $this->theme()->display('Name') ),
     1119                        'capability' => 'switch_themes',
     1120                        'priority' => 0,
     1121                ) ) );
     1122
     1123                // Themes Setting (unused - the theme is considerably more fundamental to the Customizer experience).
     1124                $this->add_setting( new WP_Customize_Filter_Setting( $this, 'active_theme', array(
     1125                        'capability' => 'switch_themes',
     1126                ) ) );
     1127
     1128                require_once( ABSPATH . 'wp-admin/includes/theme.php' );
     1129
     1130                // Theme Controls.
     1131                $themes = wp_prepare_themes_for_js();
     1132                foreach ( $themes as $theme ) {
     1133                        $theme_id = 'theme_' . $theme['id'];
     1134                        $this->add_control( new WP_Customize_Theme_Control( $this, $theme_id, array(
     1135                                'theme' => $theme,
     1136                                'section' => 'themes',
     1137                                'settings' => 'active_theme',
     1138                        ) ) );
     1139                }
     1140
     1141                $this->add_control( new WP_Customize_New_Theme_Control( $this, 'add_theme', array(
     1142                        'section' => 'themes',
     1143                        'settings' => 'active_theme',
     1144                ) ) );
     1145
    11141146                /* Site Title & Tagline */
    11151147
    11161148                $this->add_section( 'title_tagline', array(
  • src/wp-includes/class-wp-customize-section.php

     
    312312}
    313313
    314314/**
     315 * Customize Themes Section Class.
     316 *
     317 * A UI container for theme controls, which behaves like a backwards Panel.
     318 *
     319 * @package WordPress
     320 * @subpackage Customize
     321 * @since 4.2.0
     322 */
     323class WP_Customize_Themes_Section extends WP_Customize_Section {
     324
     325        public $type = 'themes';
     326
     327        /**
     328         * Render the themes section, which behaves like a panel.
     329         *
     330         * @since 4.2.0
     331         */
     332        protected function render() {
     333                $classes = 'accordion-section control-section control-section-' . $this->type;
     334                ?>
     335                <li id="accordion-section-<?php echo esc_attr( $this->id ); ?>" class="<?php echo esc_attr( $classes ); ?>">
     336                        <h3 class="accordion-section-title" tabindex="0">
     337                                <?php echo esc_html( $this->title ); ?>
     338                                <span class="screen-reader-text"><?php _e( 'Press return or enter to expand' ); ?></span>
     339                        </h3>
     340                        <span class="control-panel-back themes-panel-back" tabindex="-1"><span class="screen-reader-text"><?php _e( 'Back' ); ?></span></span>
     341                        <div class="customize-themes-panel control-panel-content themes-php">
     342                                <h2><?php esc_html_e( 'Themes' ); ?>
     343                                        <span class="title-count theme-count"><?php echo count( $this->controls ) - 1; ?></span>
     344                                <?php if ( ! is_multisite() && current_user_can( 'install_themes' ) ) : ?>
     345                                        <a href="<?php echo admin_url( 'theme-install.php' ); ?>" target="_top" class="add-new-h2"><?php echo esc_html_x( 'Add New', 'Add new theme' ); ?></a>
     346                                <?php endif; ?>
     347                                </h2>
     348                                <div class="theme-overlay" tabindex="0" role="dialog" aria-label="<?php esc_attr_e( 'Theme details' ); ?>"></div>
     349                                <div id="customize-container"></div>
     350                                <?php if ( 6 < count( $this->controls ) ) : ?>
     351                                        <p><label for="themes-filter">
     352                                                <span class="screen-reader-text"><?php _e( 'Search installed themes...' ); ?></span>
     353                                                <input type="search" id="themes-filter" placeholder="<?php esc_attr_e( 'Search installed themes...' ); ?>" />
     354                                        </label></p>
     355                                <?php endif; ?>
     356                                <div class="theme-browser rendered">
     357                                        <ul class="themes accordion-section-content">
     358                                        </ul>
     359                                </div>
     360                        </div>
     361                </li>
     362<?php }
     363}
     364
     365/**
    315366 * Customizer section representing widget area (sidebar).
    316367 *
    317368 * @package WordPress