WordPress.org

Make WordPress Core

Changeset 38829


Ignore:
Timestamp:
10/19/16 18:14:21 (8 months 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.