Make WordPress Core

Changeset 38829


Ignore:
Timestamp:
10/19/2016 06:14:21 PM (7 years ago)
Author:
westonruter
Message:

Customize: Introduce custom CSS for extending theme styles.

  • Custom CSS is associated with a given theme and is displayed in an inline style element at the wp_head hook after the wp_print_styles is called so that it overrides any enqueued stylesheets.
  • A wp_get_custom_css() function is used for accessing the CSS associated with the current theme (or another theme) and a wp_get_custom_css filter for manipulating it.
  • CSS is managed in customizer via a new "Additional CSS" section with a single textarea control.
  • WP_Customize_Section::$description_hidden is introduced for hiding extended descriptions in customizer sections behind a help toggle as done with panels.
  • CSS is stored in a custom_css post type with the theme (stylesheet) slug as the post_name.
  • WP_Customize_Custom_CSS_Setting is introduced to handle validation of CSS, previewing, and persisting the CSS to the custom_css post type.
  • The custom_css setting is tied to a new unfiltered_css capability which maps to unfiltered_html by default.
  • Escaping the message in the notification template is removed to allow markup (code tags) to be rendered.

See https://make.wordpress.org/core/2016/10/11/feature-proposal-better-theme-customizations-via-custom-css-with-live-previews/

Props johnregan3, celloexpressions, folletto, westonruter.
Fixes #35395.

Location:
trunk
Files:
2 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/css/customize-controls.css

    r38813 r38829  
    5151    border: none;
    5252    border-bottom: 1px solid #ddd;
     53    margin-bottom: 15px;
     54}
     55
     56#customize-controls .customize-info.section-meta {
    5357    margin-bottom: 15px;
    5458}
     
    125129
    126130#customize-controls .customize-info .customize-panel-description,
     131#customize-controls .customize-info .customize-section-description,
    127132#customize-controls .no-widget-areas-rendered-notice {
    128133    color: #555;
     
    132137    border-top: 1px solid #ddd;
    133138}
     139
    134140#customize-controls .customize-info .customize-panel-description.open + .no-widget-areas-rendered-notice {
    135141    border-top: none;
    136142}
    137143
    138 #customize-controls .customize-info .customize-panel-description p:first-child {
     144#customize-controls .customize-info .customize-section-description {
     145    margin-bottom: 15px;
     146}
     147
     148#customize-controls .customize-info .customize-panel-description p:first-child,
     149#customize-controls .customize-info .customize-section-description p:first-child {
    139150    margin-top: 0;
    140151}
    141152
    142 #customize-controls .customize-info .customize-panel-description p:last-child {
     153#customize-controls .customize-info .customize-panel-description p:last-child,
     154#customize-controls .customize-info .customize-section-description p:last-child {
    143155    margin-bottom: 0;
    144156}
     
    326338div.customize-section-description {
    327339    margin-top: 22px;
     340}
     341
     342.customize-info div.customize-section-description {
     343    margin-top: 0;
    328344}
    329345
     
    543559}
    544560
     561.customize-section-description a.external-link:after {
     562    font: 16px/11px dashicons;
     563    content: "\f310";
     564    top: 3px;
     565    position: relative;
     566    padding-left: 3px;
     567    display: inline-block;
     568    text-decoration: none;
     569}
     570
    545571.customize-control-color .color-picker,
    546572.customize-control-upload div {
     
    961987.customize-control-header .new {
    962988    float: right;
     989}
     990
     991/**
     992 * Custom CSS Section
     993 *
     994 * Modifications to the Section Container to
     995 * make the textarea full-width.
     996 */
     997#customize-theme-controls #sub-accordion-section-custom_css {
     998    padding-left: 0;
     999    padding-right: 0;
     1000}
     1001
     1002#customize-theme-controls #sub-accordion-section-custom_css .customize-section-title {
     1003    margin-left: 0;
     1004}
     1005
     1006#customize-theme-controls #sub-accordion-section-custom_css .customize-control-title,
     1007#customize-theme-controls #sub-accordion-section-custom_css .notice {
     1008    margin-left: 10px;
     1009    margin-right: 10px;
     1010}
     1011
     1012#sub-accordion-section-custom_css .customize-control-notifications-container {
     1013    margin-bottom: 15px;
     1014}
     1015
     1016#sub-accordion-section-custom_css textarea {
     1017    border-right: 0;
     1018    border-left: 0;
     1019    font-family: Consolas, Monaco, monospace;
     1020    font-size: 12px;
     1021    padding: 6px 8px;
     1022    height: 553px;
    9631023}
    9641024
  • trunk/src/wp-admin/js/customize-controls.js

    r38813 r38829  
    905905         */
    906906        attachEvents: function () {
    907             var section = this;
     907            var meta, content, section = this;
    908908
    909909            // Expand/Collapse accordion sections on click.
     
    919919                    section.expand();
    920920                }
     921            });
     922
     923            // This is very similar to what is found for api.Panel.attachEvents().
     924            section.container.find( '.customize-section-title .customize-help-toggle' ).on( 'click', function() {
     925
     926                meta = section.container.find( '.section-meta' );
     927                if ( meta.hasClass( 'cannot-expand' ) ) {
     928                    return;
     929                }
     930                content = meta.find( '.customize-section-description:first' );
     931                content.toggleClass( 'open' );
     932                content.slideToggle();
     933                content.attr( 'aria-expanded', function ( i, attr ) {
     934                    return attr === 'true' ? 'false' : 'true';
     935                });
    921936            });
    922937        },
  • trunk/src/wp-includes/capabilities.php

    r38698 r38829  
    330330            $caps[] = $cap;
    331331        break;
     332    case 'unfiltered_css' :
     333        $caps[] = 'unfiltered_html';
     334        break;
    332335    case 'edit_files':
    333336    case 'edit_plugins':
  • trunk/src/wp-includes/class-wp-customize-manager.php

    r38813 r38829  
    301301        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-new-menu-section.php' );
    302302
     303        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-custom-css-setting.php' );
    303304        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
    304305        require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
     
    24942495            <ul>
    24952496                <# _.each( data.notifications, function( notification ) { #>
    2496                     <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{ notification.message || notification.code }}</li>
     2497                    <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">{{{ notification.message || notification.code }}}</li>
    24972498                <# } ); #>
    24982499            </ul>
     
    32213222        ) ) );
    32223223
    3223 
    32243224        /* Custom Header */
    32253225
     
    33673367            'type' => 'dropdown-pages',
    33683368        ) );
     3369
     3370        /* Custom CSS */
     3371        $this->add_section( 'custom_css', array(
     3372            'title'              => __( 'Additional CSS' ),
     3373            'priority'           => 140,
     3374            'description_hidden' => true,
     3375            'description'        => sprintf( '%s<br /><a href="%s" class="external-link" target="_blank">%s<span class="screen-reader-text">%s</span></a>',
     3376                __( 'CSS allows you to customize the appearance and layout of your site with code. Separate CSS is saved for each of your themes.' ),
     3377                'https://codex.wordpress.org/Know_Your_Sources#CSS',
     3378                __( 'Learn more about CSS' ),
     3379                __( '(link opens in a new window)' )
     3380            ),
     3381        ) );
     3382
     3383        $custom_css_setting = new WP_Customize_Custom_CSS_Setting( $this, sprintf( 'custom_css[%s]', get_stylesheet() ), array(
     3384            'capability' => 'unfiltered_css',
     3385        ) );
     3386        $this->add_setting( $custom_css_setting );
     3387
     3388        $this->add_control( 'custom_css', array(
     3389            'type'     => 'textarea',
     3390            'section'  => 'custom_css',
     3391            'settings' => array( 'default' => $custom_css_setting->id ),
     3392        ) );
    33693393    }
    33703394
     
    33893413            }
    33903414        }
    3391 
    33923415        return 0 !== count( get_pages() );
    33933416    }
  • trunk/src/wp-includes/class-wp-customize-section.php

    r38470 r38829  
    147147
    148148    /**
     149     * Show the description or hide it behind the help icon.
     150     *
     151     * @since 4.7.0
     152     * @access public
     153     *
     154     * @var bool Indicates whether the Section's description should be
     155     *           hidden behind a help icon ("?") in the Section header,
     156     *           similar to how help icons are displayed on Panels.
     157     */
     158    public $description_hidden = false;
     159
     160    /**
    149161     * Constructor.
    150162     *
     
    224236     */
    225237    public function json() {
    226         $array = wp_array_slice_assoc( (array) $this, array( 'id', 'description', 'priority', 'panel', 'type' ) );
     238        $array = wp_array_slice_assoc( (array) $this, array( 'id', 'description', 'priority', 'panel', 'type', 'description_hidden' ) );
    227239        $array['title'] = html_entity_decode( $this->title, ENT_QUOTES, get_bloginfo( 'charset' ) );
    228240        $array['content'] = $this->get_content();
     
    325337     */
    326338    public function print_template() {
    327         ?>
     339        ?>
    328340        <script type="text/html" id="tmpl-customize-section-<?php echo $this->type; ?>">
    329341            <?php $this->render_template(); ?>
    330342        </script>
    331         <?php
     343        <?php
    332344    }
    333345
     
    351363            </h3>
    352364            <ul class="accordion-section-content">
    353                 <li class="customize-section-description-container">
     365                <li class="customize-section-description-container section-meta <# if ( data.description_hidden ) { #>customize-info<# } #>">
    354366                    <div class="customize-section-title">
    355367                        <button class="customize-section-back" tabindex="-1">
     
    362374                            {{ data.title }}
    363375                        </h3>
     376                        <# if ( data.description && data.description_hidden ) { #>
     377                            <button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button>
     378                            <div class="description customize-section-description">
     379                                {{{ data.description }}}
     380                            </div>
     381                        <# } #>
    364382                    </div>
    365                     <# if ( data.description ) { #>
     383
     384                    <# if ( data.description && ! data.description_hidden ) { #>
    366385                        <div class="description customize-section-description">
    367386                            {{{ data.description }}}
  • trunk/src/wp-includes/default-filters.php

    r38810 r38829  
    246246add_action( 'wp_head',             'rel_canonical'                          );
    247247add_action( 'wp_head',             'wp_shortlink_wp_head',            10, 0 );
     248add_action( 'wp_head',             'wp_custom_css_cb',                11    );
    248249add_action( 'wp_head',             'wp_site_icon',                    99    );
    249250add_action( 'wp_footer',           'wp_print_footer_scripts',         20    );
  • trunk/src/wp-includes/js/customize-preview.js

    r38811 r38829  
    787787            setting.bind( function( attachmentId ) {
    788788                $( 'body' ).toggleClass( 'wp-custom-logo', !! attachmentId );
     789            });
     790        });
     791
     792        api( 'custom_css[' + api.settings.theme.stylesheet + ']', function( value ) {
     793            value.bind( function( to ) {
     794                $( '#wp-custom-css' ).text( to );
    789795            } );
    790796        } );
  • trunk/src/wp-includes/post.php

    r38810 r38829  
    110110        'delete_with_user' => false,
    111111        'query_var' => false,
     112    ) );
     113
     114    register_post_type( 'custom_css', array(
     115        'labels' => array(
     116            'name'          => __( 'Custom CSS' ),
     117            'singular_name' => __( 'Custom CSS' ),
     118        ),
     119        'public'           => false,
     120        'hierarchical'     => false,
     121        'rewrite'          => false,
     122        'query_var'        => false,
     123        'delete_with_user' => false,
     124        'can_export'       => true,
     125        '_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
     126        'supports'         => array( 'title' ),
     127        'capabilities'     => array(
     128            'delete_posts'           => 'edit_theme_options',
     129            'delete_post'            => 'edit_theme_options',
     130            'delete_published_posts' => 'edit_theme_options',
     131            'delete_private_posts'   => 'edit_theme_options',
     132            'delete_others_posts'    => 'edit_theme_options',
     133            'edit_post'              => 'unfiltered_css',
     134            'edit_posts'             => 'unfiltered_css',
     135            'edit_others_posts'      => 'unfiltered_css',
     136            'edit_published_posts'   => 'unfiltered_css',
     137            'read_post'              => 'read',
     138            'read_private_posts'     => 'read',
     139            'publish_posts'          => 'edit_theme_options',
     140        ),
    112141    ) );
    113142
  • trunk/src/wp-includes/theme.php

    r38810 r38829  
    14021402
    14031403/**
     1404 * Render the Custom CSS style element.
     1405 *
     1406 * @since 4.7.0
     1407 * @access public
     1408 */
     1409function wp_custom_css_cb() {
     1410    $styles = wp_get_custom_css();
     1411    if ( $styles || is_customize_preview() ) : ?>
     1412        <style type="text/css" id="wp-custom-css">
     1413            <?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
     1414        </style>
     1415    <?php endif;
     1416}
     1417
     1418/**
     1419 * Fetch the saved Custom CSS content.
     1420 *
     1421 * Gets the content of a Custom CSS post that matches the
     1422 * current theme.
     1423 *
     1424 * @since 4.7.0
     1425 * @access public
     1426 *
     1427 * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
     1428 *
     1429 * @return string The Custom CSS Post content.
     1430 */
     1431function wp_get_custom_css( $stylesheet = '' ) {
     1432    $css = '';
     1433
     1434    if ( empty( $stylesheet ) ) {
     1435        $stylesheet = get_stylesheet();
     1436    }
     1437
     1438    $custom_css_query_vars = array(
     1439        'post_type' => 'custom_css',
     1440        'post_status' => get_post_stati(),
     1441        'name' => sanitize_title( $stylesheet ),
     1442        'number' => 1,
     1443        'no_found_rows' => true,
     1444        'cache_results' => true,
     1445        'update_post_meta_cache' => false,
     1446        'update_term_meta_cache' => false,
     1447    );
     1448
     1449    $post = null;
     1450    if ( get_stylesheet() === $stylesheet ) {
     1451        $post_id = get_theme_mod( 'custom_css_post_id' );
     1452        if ( ! $post_id ) {
     1453            $query = new WP_Query( $custom_css_query_vars );
     1454            $post = $query->post;
     1455
     1456            /*
     1457             * Cache the lookup. See WP_Customize_Custom_CSS_Setting::update().
     1458             * @todo This should get cleared if a custom_css post is added/removed.
     1459             */
     1460            set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
     1461        } elseif ( $post_id > 0 ) {
     1462            $post = get_post( $post_id );
     1463        }
     1464    } else {
     1465        $query = new WP_Query( $custom_css_query_vars );
     1466        $post = $query->post;
     1467    }
     1468
     1469    if ( $post ) {
     1470        $css = $post->post_content;
     1471    }
     1472
     1473    /**
     1474     * Modify the Custom CSS Output into the <head>.
     1475     *
     1476     * @since 4.7.0
     1477     *
     1478     * @param string $css        CSS pulled in from the Custom CSS CPT.
     1479     * @param string $stylesheet The theme stylesheet name.
     1480     */
     1481    $css = apply_filters( 'wp_get_custom_css', $css, $stylesheet );
     1482
     1483    return $css;
     1484}
     1485
     1486/**
    14041487 * Add callback for custom TinyMCE editor stylesheets.
    14051488 *
  • trunk/tests/phpunit/tests/user/capabilities.php

    r38802 r38829  
    392392            $expected['unfiltered_upload'],
    393393            $expected['unfiltered_html'],
     394            $expected['unfiltered_css'],
    394395            $expected['edit_files'],
    395396            $expected['edit_plugins'],
Note: See TracChangeset for help on using the changeset viewer.