Make WordPress Core

Changeset 38829


Ignore:
Timestamp:
10/19/2016 06:14:21 PM (10 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.