WordPress.org

Make WordPress Core

Changeset 50973


Ignore:
Timestamp:
05/24/2021 05:38:59 PM (4 months ago)
Author:
jorgefilipecosta
Message:

Block Editor: Add Global Styles support using theme.json file.

This is the second piece of landing the theme.json processing in WordPress core.
It includes the mechanism that outputs the CSS styles of a theme.json file.

Props nosolosw, youknowriad.
See #53175.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-editor.php

    r50963 r50973  
    244244    );
    245245
    246     $editor_settings['__experimentalFeatures'] = WP_Theme_JSON_Resolver::get_merged_data( $editor_settings )->get_settings();
    247 
     246    $theme_json = WP_Theme_JSON_Resolver::get_merged_data( $editor_settings );
     247
     248    if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
     249        $editor_settings['styles'][] = array( 'css' => $theme_json->get_stylesheet( 'block_styles' ) );
     250        $editor_settings['styles'][] = array(
     251            'css'                     => $theme_json->get_stylesheet( 'css_variables' ),
     252            '__experimentalNoWrapper' => true,
     253        );
     254    }
     255
     256    $editor_settings['__experimentalFeatures'] = $theme_json->get_settings();
    248257    // These settings may need to be updated based on data coming from theme.json sources.
    249258    if ( isset( $editor_settings['__experimentalFeatures']['color']['palette'] ) ) {
  • trunk/src/wp-includes/class-wp-theme-json.php

    r50967 r50973  
    2424
    2525    /**
    26      * Holds the allowed block names extracted from block.json.
    27      * Shared among all instances so we only process it once.
     26     * Holds block metadata extracted from block.json
     27     * to be shared among all instances so we don't
     28     * process it twice.
    2829     *
    2930     * @since 5.8.0
    3031     * @var array
    3132     */
    32     private static $allowed_block_names = null;
    33 
     33    private static $blocks_metadata = null;
     34
     35    /**
     36     * The CSS selector for the top-level styles.
     37     *
     38     * @since 5.8.0
     39     * @var string
     40     */
     41    const ROOT_BLOCK_SELECTOR = 'body';
     42
     43    /**
     44     * Presets are a set of values that serve
     45     * to bootstrap some styles: colors, font sizes, etc.
     46     *
     47     * They are a unkeyed array of values such as:
     48     *
     49     * ```php
     50     * array(
     51     *   array(
     52     *     'slug'      => 'unique-name-within-the-set',
     53     *     'name'      => 'Name for the UI',
     54     *     <value_key> => 'value'
     55     *   ),
     56     * )
     57     * ```
     58     *
     59     * This contains the necessary metadata to process them:
     60     *
     61     * - path          => where to find the preset within the settings section
     62     *
     63     * - value_key     => the key that represents the value
     64     *
     65     * - css_var_infix => infix to use in generating the CSS Custom Property. Example:
     66     *                   --wp--preset--<preset_infix>--<slug>: <preset_value>
     67     *
     68     * - classes      => array containing a structure with the classes to
     69     *                   generate for the presets. Each class should have
     70     *                   the class suffix and the property name. Example:
     71     *
     72     *                   .has-<slug>-<class_suffix> {
     73     *                       <property_name>: <preset_value>
     74     *                   }
     75     *
     76     * @since 5.8.0
     77     * @var array
     78     */
     79    const PRESETS_METADATA = array(
     80        array(
     81            'path'          => array( 'color', 'palette' ),
     82            'value_key'     => 'color',
     83            'css_var_infix' => 'color',
     84            'classes'       => array(
     85                array(
     86                    'class_suffix'  => 'color',
     87                    'property_name' => 'color',
     88                ),
     89                array(
     90                    'class_suffix'  => 'background-color',
     91                    'property_name' => 'background-color',
     92                ),
     93            ),
     94        ),
     95        array(
     96            'path'          => array( 'color', 'gradients' ),
     97            'value_key'     => 'gradient',
     98            'css_var_infix' => 'gradient',
     99            'classes'       => array(
     100                array(
     101                    'class_suffix'  => 'gradient-background',
     102                    'property_name' => 'background',
     103                ),
     104            ),
     105        ),
     106        array(
     107            'path'          => array( 'typography', 'fontSizes' ),
     108            'value_key'     => 'size',
     109            'css_var_infix' => 'font-size',
     110            'classes'       => array(
     111                array(
     112                    'class_suffix'  => 'font-size',
     113                    'property_name' => 'font-size',
     114                ),
     115            ),
     116        ),
     117    );
     118
     119    /**
     120     * Metadata for style properties.
     121     *
     122     * Each property declares:
     123     *
     124     * - 'value': path to the value in theme.json and block attributes.
     125     *
     126     * @since 5.8.0
     127     * @var array
     128     */
     129    const PROPERTIES_METADATA = array(
     130        'background'       => array(
     131            'value' => array( 'color', 'gradient' ),
     132        ),
     133        'background-color' => array(
     134            'value' => array( 'color', 'background' ),
     135        ),
     136        'color'            => array(
     137            'value' => array( 'color', 'text' ),
     138        ),
     139        'font-size'        => array(
     140            'value' => array( 'typography', 'fontSize' ),
     141        ),
     142        'line-height'      => array(
     143            'value' => array( 'typography', 'lineHeight' ),
     144        ),
     145        'margin'           => array(
     146            'value'      => array( 'spacing', 'margin' ),
     147            'properties' => array( 'top', 'right', 'bottom', 'left' ),
     148        ),
     149        'padding'          => array(
     150            'value'      => array( 'spacing', 'padding' ),
     151            'properties' => array( 'top', 'right', 'bottom', 'left' ),
     152        ),
     153    );
     154
     155    /**
     156     * @since 5.8.0
     157     * @var array
     158     */
    34159    const ALLOWED_TOP_LEVEL_KEYS = array(
     160        'settings',
     161        'styles',
    35162        'version',
    36         'settings',
    37163    );
    38164
     165    /**
     166     * @since 5.8.0
     167     * @var array
     168     */
    39169    const ALLOWED_SETTINGS = array(
    40170        'color'      => array(
     
    61191    );
    62192
     193    /**
     194     * @since 5.8.0
     195     * @var array
     196     */
     197    const ALLOWED_STYLES = array(
     198        'color'      => array(
     199            'background' => null,
     200            'gradient'   => null,
     201            'text'       => null,
     202        ),
     203        'spacing'    => array(
     204            'margin'  => array(
     205                'top'    => null,
     206                'right'  => null,
     207                'bottom' => null,
     208                'left'   => null,
     209            ),
     210            'padding' => array(
     211                'bottom' => null,
     212                'left'   => null,
     213                'right'  => null,
     214                'top'    => null,
     215            ),
     216        ),
     217        'typography' => array(
     218            'fontSize'   => null,
     219            'lineHeight' => null,
     220        ),
     221    );
     222
     223    /**
     224     * @since 5.8.0
     225     * @var array
     226     */
     227    const ELEMENTS = array(
     228        'link' => 'a',
     229        'h1'   => 'h1',
     230        'h2'   => 'h2',
     231        'h3'   => 'h3',
     232        'h4'   => 'h4',
     233        'h5'   => 'h5',
     234        'h6'   => 'h6',
     235    );
     236
     237    /**
     238     * @since 5.8.0
     239     * @var int
     240     */
    63241    const LATEST_SCHEMA = 1;
    64242
     
    80258
    81259    /**
    82      * Returns the allowed block names.
    83      *
    84      * @since 5.8.0
    85      *
    86      * @return array
    87      */
    88     private static function get_allowed_block_names() {
    89         if ( null !== self::$allowed_block_names ) {
    90             return self::$allowed_block_names;
    91         }
    92 
    93         self::$allowed_block_names = array_keys( WP_Block_Type_Registry::get_instance()->get_all_registered() );
    94 
    95         return self::$allowed_block_names;
    96     }
    97 
    98     /**
    99260     * Sanitizes the input according to the schemas.
    100261     *
     
    111272        }
    112273
    113         $allowed_blocks = self::get_allowed_block_names();
    114 
    115         $output = array_intersect_key( $input, array_flip( self::ALLOWED_TOP_LEVEL_KEYS ) );
     274        $allowed_top_level_keys = self::ALLOWED_TOP_LEVEL_KEYS;
     275        $allowed_settings       = self::ALLOWED_SETTINGS;
     276        $allowed_styles         = self::ALLOWED_STYLES;
     277        $allowed_blocks         = array_keys( self::get_blocks_metadata() );
     278        $allowed_elements       = array_keys( self::ELEMENTS );
     279
     280        $output = array_intersect_key( $input, array_flip( $allowed_top_level_keys ) );
    116281
    117282        // Build the schema.
    118283        $schema                 = array();
     284        $schema_styles_elements = array();
     285        foreach ( $allowed_elements as $element ) {
     286            $schema_styles_elements[ $element ] = $allowed_styles;
     287        }
     288        $schema_styles_blocks   = array();
    119289        $schema_settings_blocks = array();
    120290        foreach ( $allowed_blocks as $block ) {
    121             $schema_settings_blocks[ $block ] = self::ALLOWED_SETTINGS;
    122         }
    123         $schema['settings']           = self::ALLOWED_SETTINGS;
     291            $schema_settings_blocks[ $block ]           = $allowed_settings;
     292            $schema_styles_blocks[ $block ]             = $allowed_styles;
     293            $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements;
     294        }
     295        $schema['styles']             = $allowed_styles;
     296        $schema['styles']['blocks']   = $schema_styles_blocks;
     297        $schema['styles']['elements'] = $schema_styles_elements;
     298        $schema['settings']           = $allowed_settings;
    124299        $schema['settings']['blocks'] = $schema_settings_blocks;
    125300
    126301        // Remove anything that's not present in the schema.
    127         foreach ( array( 'settings' ) as $subtree ) {
     302        foreach ( array( 'styles', 'settings' ) as $subtree ) {
    128303            if ( ! isset( $input[ $subtree ] ) ) {
    129304                continue;
     
    148323
    149324    /**
     325     * Returns the metadata for each block.
     326     *
     327     * Example:
     328     *
     329     * {
     330     *   'core/paragraph': {
     331     *     'selector': 'p',
     332     *     'elements': {
     333     *       'link' => 'link selector',
     334     *       'etc'  => 'element selector'
     335     *     }
     336     *   },
     337     *   'core/heading': {
     338     *     'selector': 'h1',
     339     *     'elements': {}
     340     *   }
     341     *   'core/group': {
     342     *     'selector': '.wp-block-group',
     343     *     'elements': {}
     344     *   }
     345     * }
     346     *
     347     * @since 5.8.0
     348     *
     349     * @return array Block metadata.
     350     */
     351    private static function get_blocks_metadata() {
     352        if ( null !== self::$blocks_metadata ) {
     353            return self::$blocks_metadata;
     354        }
     355
     356        self::$blocks_metadata = array();
     357
     358        $registry = WP_Block_Type_Registry::get_instance();
     359        $blocks   = $registry->get_all_registered();
     360        foreach ( $blocks as $block_name => $block_type ) {
     361            if (
     362                isset( $block_type->supports['__experimentalSelector'] ) &&
     363                is_string( $block_type->supports['__experimentalSelector'] )
     364            ) {
     365                self::$blocks_metadata[ $block_name ]['selector'] = $block_type->supports['__experimentalSelector'];
     366            } else {
     367                self::$blocks_metadata[ $block_name ]['selector'] = '.wp-block-' . str_replace( '/', '-', str_replace( 'core/', '', $block_name ) );
     368            }
     369
     370            // Assign defaults, then overwrite those that the block sets by itself.
     371            // If the block selector is compounded, will append the element to each
     372            // individual block selector.
     373            $block_selectors = explode( ',', self::$blocks_metadata[ $block_name ]['selector'] );
     374            foreach ( self::ELEMENTS as $el_name => $el_selector ) {
     375                $element_selector = array();
     376                foreach ( $block_selectors as $selector ) {
     377                    $element_selector[] = $selector . ' ' . $el_selector;
     378                }
     379                self::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector );
     380            }
     381        }
     382
     383        return self::$blocks_metadata;
     384    }
     385
     386    /**
    150387     * Given a tree, removes the keys that are not present in the schema.
    151388     *
     
    156393     * @param array $tree Input to process.
    157394     * @param array $schema Schema to adhere to.
     395     *
    158396     * @return array Returns the modified $tree.
    159397     */
     
    211449
    212450    /**
     451     * Returns the stylesheet that results of processing
     452     * the theme.json structure this object represents.
     453     *
     454     * @since 5.8.0
     455     *
     456     * @param string $type Type of stylesheet we want accepts 'all', 'block_styles', and 'css_variables'.
     457     *
     458     * @return string Stylesheet.
     459     */
     460    public function get_stylesheet( $type = 'all' ) {
     461        $blocks_metadata = self::get_blocks_metadata();
     462        $style_nodes     = self::get_style_nodes( $this->theme_json, $blocks_metadata );
     463        $setting_nodes   = self::get_setting_nodes( $this->theme_json, $blocks_metadata );
     464
     465        switch ( $type ) {
     466            case 'block_styles':
     467                return $this->get_block_styles( $style_nodes, $setting_nodes );
     468            case 'css_variables':
     469                return $this->get_css_variables( $setting_nodes );
     470            default:
     471                return $this->get_css_variables( $setting_nodes ) . $this->get_block_styles( $style_nodes, $setting_nodes );
     472        }
     473
     474    }
     475
     476    /**
     477     * Converts each style section into a list of rulesets
     478     * containing the block styles to be appended to the stylesheet.
     479     *
     480     * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax
     481     *
     482     * For each section this creates a new ruleset such as:
     483     *
     484     *   block-selector {
     485     *     style-property-one: value;
     486     *   }
     487     *
     488     * Additionally, it'll also create new rulesets
     489     * as classes for each preset value such as:
     490     *
     491     *   .has-value-color {
     492     *     color: value;
     493     *   }
     494     *
     495     *   .has-value-background-color {
     496     *     background-color: value;
     497     *   }
     498     *
     499     *   .has-value-font-size {
     500     *     font-size: value;
     501     *   }
     502     *
     503     *   .has-value-gradient-background {
     504     *     background: value;
     505     *   }
     506     *
     507     *   p.has-value-gradient-background {
     508     *     background: value;
     509     *   }
     510     *
     511     * @since 5.8.0
     512     *
     513     * @param array $style_nodes Nodes with styles.
     514     * @param array $setting_nodes Nodes with settings.
     515     *
     516     * @return string The new stylesheet.
     517     */
     518    private function get_block_styles( $style_nodes, $setting_nodes ) {
     519        $block_rules = '';
     520        foreach ( $style_nodes as $metadata ) {
     521            if ( null === $metadata['selector'] ) {
     522                continue;
     523            }
     524
     525            $node         = _wp_array_get( $this->theme_json, $metadata['path'], array() );
     526            $selector     = $metadata['selector'];
     527            $declarations = self::compute_style_properties( $node );
     528            $block_rules .= self::to_ruleset( $selector, $declarations );
     529        }
     530
     531        $preset_rules = '';
     532        foreach ( $setting_nodes as $metadata ) {
     533            if ( null === $metadata['selector'] ) {
     534                continue;
     535            }
     536
     537            $selector      = $metadata['selector'];
     538            $node          = _wp_array_get( $this->theme_json, $metadata['path'], array() );
     539            $preset_rules .= self::compute_preset_classes( $node, $selector );
     540        }
     541
     542        return $block_rules . $preset_rules;
     543    }
     544
     545    /**
     546     * Converts each styles section into a list of rulesets
     547     * to be appended to the stylesheet.
     548     * These rulesets contain all the css variables (custom variables and preset variables).
     549     *
     550     * See glossary at https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax
     551     *
     552     * For each section this creates a new ruleset such as:
     553     *
     554     *   block-selector {
     555     *     --wp--preset--category--slug: value;
     556     *     --wp--custom--variable: value;
     557     *   }
     558     *
     559     * @since 5.8.0
     560     *
     561     * @param array $nodes Nodes with settings.
     562     *
     563     * @return string The new stylesheet.
     564     */
     565    private function get_css_variables( $nodes ) {
     566        $stylesheet = '';
     567        foreach ( $nodes as $metadata ) {
     568            if ( null === $metadata['selector'] ) {
     569                continue;
     570            }
     571
     572            $selector = $metadata['selector'];
     573
     574            $node         = _wp_array_get( $this->theme_json, $metadata['path'], array() );
     575            $declarations = array_merge( self::compute_preset_vars( $node ), self::compute_theme_vars( $node ) );
     576
     577            $stylesheet .= self::to_ruleset( $selector, $declarations );
     578        }
     579
     580        return $stylesheet;
     581    }
     582
     583    /**
     584     * Given a selector and a declaration list,
     585     * creates the corresponding ruleset.
     586     *
     587     * To help debugging, will add some space
     588     * if SCRIPT_DEBUG is defined and true.
     589     *
     590     * @since 5.8.0
     591     *
     592     * @param string $selector CSS selector.
     593     * @param array  $declarations List of declarations.
     594     *
     595     * @return string CSS ruleset.
     596     */
     597    private static function to_ruleset( $selector, $declarations ) {
     598        if ( empty( $declarations ) ) {
     599            return '';
     600        }
     601
     602        $declaration_block = array_reduce(
     603            $declarations,
     604            function ( $carry, $element ) {
     605                return $carry .= $element['name'] . ': ' . $element['value'] . ';'; },
     606            ''
     607        );
     608
     609        return $selector . '{' . $declaration_block . '}';
     610    }
     611
     612    /**
     613     * Given a settings array, it returns the generated rulesets
     614     * for the preset classes.
     615     *
     616     * @since 5.8.0
     617     *
     618     * @param array  $settings Settings to process.
     619     * @param string $selector Selector wrapping the classes.
     620     *
     621     * @return string The result of processing the presets.
     622     */
     623    private static function compute_preset_classes( $settings, $selector ) {
     624        if ( self::ROOT_BLOCK_SELECTOR === $selector ) {
     625            // Classes at the global level do not need any CSS prefixed,
     626            // and we don't want to increase its specificity.
     627            $selector = '';
     628        }
     629
     630        $stylesheet = '';
     631        foreach ( self::PRESETS_METADATA as $preset ) {
     632            $values = _wp_array_get( $settings, $preset['path'], array() );
     633            foreach ( $values as $value ) {
     634                foreach ( $preset['classes'] as $class ) {
     635                    $stylesheet .= self::to_ruleset(
     636                        $selector . '.has-' . $value['slug'] . '-' . $class['class_suffix'],
     637                        array(
     638                            array(
     639                                'name'  => $class['property_name'],
     640                                'value' => $value[ $preset['value_key'] ] . ' !important',
     641                            ),
     642                        )
     643                    );
     644                }
     645            }
     646        }
     647
     648        return $stylesheet;
     649    }
     650
     651    /**
     652     * Given the block settings, it extracts the CSS Custom Properties
     653     * for the presets and adds them to the $declarations array
     654     * following the format:
     655     *
     656     * ```php
     657     * array(
     658     *   'name'  => 'property_name',
     659     *   'value' => 'property_value,
     660     * )
     661     * ```
     662     *
     663     * @since 5.8.0
     664     *
     665     * @param array $settings Settings to process.
     666     *
     667     * @return array Returns the modified $declarations.
     668     */
     669    private static function compute_preset_vars( $settings ) {
     670        $declarations = array();
     671        foreach ( self::PRESETS_METADATA as $preset ) {
     672            $values = _wp_array_get( $settings, $preset['path'], array() );
     673            foreach ( $values as $value ) {
     674                $declarations[] = array(
     675                    'name'  => '--wp--preset--' . $preset['css_var_infix'] . '--' . $value['slug'],
     676                    'value' => $value[ $preset['value_key'] ],
     677                );
     678            }
     679        }
     680
     681        return $declarations;
     682    }
     683
     684    /**
     685     * Given an array of settings, it extracts the CSS Custom Properties
     686     * for the custom values and adds them to the $declarations
     687     * array following the format:
     688     *
     689     * ```php
     690     * array(
     691     *   'name'  => 'property_name',
     692     *   'value' => 'property_value,
     693     * )
     694     * ```
     695     *
     696     * @since 5.8.0
     697     *
     698     * @param array $settings Settings to process.
     699     *
     700     * @return array Returns the modified $declarations.
     701     */
     702    private static function compute_theme_vars( $settings ) {
     703        $declarations  = array();
     704        $custom_values = _wp_array_get( $settings, array( 'custom' ), array() );
     705        $css_vars      = self::flatten_tree( $custom_values );
     706        foreach ( $css_vars as $key => $value ) {
     707            $declarations[] = array(
     708                'name'  => '--wp--custom--' . $key,
     709                'value' => $value,
     710            );
     711        }
     712
     713        return $declarations;
     714    }
     715
     716    /**
     717     * Given a tree, it creates a flattened one
     718     * by merging the keys and binding the leaf values
     719     * to the new keys.
     720     *
     721     * It also transforms camelCase names into kebab-case
     722     * and substitutes '/' by '-'.
     723     *
     724     * This is thought to be useful to generate
     725     * CSS Custom Properties from a tree,
     726     * although there's nothing in the implementation
     727     * of this function that requires that format.
     728     *
     729     * For example, assuming the given prefix is '--wp'
     730     * and the token is '--', for this input tree:
     731     *
     732     * {
     733     *   'some/property': 'value',
     734     *   'nestedProperty': {
     735     *     'sub-property': 'value'
     736     *   }
     737     * }
     738     *
     739     * it'll return this output:
     740     *
     741     * {
     742     *   '--wp--some-property': 'value',
     743     *   '--wp--nested-property--sub-property': 'value'
     744     * }
     745     *
     746     * @since 5.8.0
     747     *
     748     * @param array  $tree Input tree to process.
     749     * @param string $prefix Prefix to prepend to each variable. '' by default.
     750     * @param string $token Token to use between levels. '--' by default.
     751     *
     752     * @return array The flattened tree.
     753     */
     754    private static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
     755        $result = array();
     756        foreach ( $tree as $property => $value ) {
     757            $new_key = $prefix . str_replace(
     758                '/',
     759                '-',
     760                strtolower( preg_replace( '/(?<!^)[A-Z]/', '-$0', $property ) ) // CamelCase to kebab-case.
     761            );
     762
     763            if ( is_array( $value ) ) {
     764                $new_prefix = $new_key . $token;
     765                $result     = array_merge(
     766                    $result,
     767                    self::flatten_tree( $value, $new_prefix, $token )
     768                );
     769            } else {
     770                $result[ $new_key ] = $value;
     771            }
     772        }
     773        return $result;
     774    }
     775
     776    /**
     777     * Given a styles array, it extracts the style properties
     778     * and adds them to the $declarations array following the format:
     779     *
     780     * ```php
     781     * array(
     782     *   'name'  => 'property_name',
     783     *   'value' => 'property_value,
     784     * )
     785     * ```
     786     *
     787     * @since 5.8.0
     788     *
     789     * @param array $styles Styles to process.
     790     *
     791     * @return array Returns the modified $declarations.
     792     */
     793    private static function compute_style_properties( $styles ) {
     794        $declarations = array();
     795        if ( empty( $styles ) ) {
     796            return $declarations;
     797        }
     798
     799        $properties = array();
     800        foreach ( self::PROPERTIES_METADATA as $name => $metadata ) {
     801            // Some properties can be shorthand properties, meaning that
     802            // they contain multiple values instead of a single one.
     803            // An example of this is the padding property.
     804            if ( self::has_properties( $metadata ) ) {
     805                foreach ( $metadata['properties'] as $property ) {
     806                    $properties[] = array(
     807                        'name'  => $name . '-' . $property,
     808                        'value' => array_merge( $metadata['value'], array( $property ) ),
     809                    );
     810                }
     811            } else {
     812                $properties[] = array(
     813                    'name'  => $name,
     814                    'value' => $metadata['value'],
     815                );
     816            }
     817        }
     818
     819        foreach ( $properties as $prop ) {
     820            $value = self::get_property_value( $styles, $prop['value'] );
     821            if ( empty( $value ) ) {
     822                continue;
     823            }
     824
     825            $declarations[] = array(
     826                'name'  => $prop['name'],
     827                'value' => $value,
     828            );
     829        }
     830
     831        return $declarations;
     832    }
     833
     834    /**
     835     * Whether the metadata contains a key named properties.
     836     *
     837     * @since 5.8.0
     838     *
     839     * @param array $metadata Description of the style property.
     840     *
     841     * @return boolean True if properties exists, false otherwise.
     842     */
     843    private static function has_properties( $metadata ) {
     844        if ( array_key_exists( 'properties', $metadata ) ) {
     845            return true;
     846        }
     847
     848        return false;
     849    }
     850
     851    /**
     852     * Returns the style property for the given path.
     853     *
     854     * It also converts CSS Custom Property stored as
     855     * "var:preset|color|secondary" to the form
     856     * "--wp--preset--color--secondary".
     857     *
     858     * @since 5.8.0
     859     *
     860     * @param array $styles Styles subtree.
     861     * @param array $path Which property to process.
     862     *
     863     * @return string Style property value.
     864     */
     865    private static function get_property_value( $styles, $path ) {
     866        $value = _wp_array_get( $styles, $path, '' );
     867
     868        if ( '' === $value ) {
     869            return $value;
     870        }
     871
     872        $prefix     = 'var:';
     873        $prefix_len = strlen( $prefix );
     874        $token_in   = '|';
     875        $token_out  = '--';
     876        if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) {
     877            $unwrapped_name = str_replace(
     878                $token_in,
     879                $token_out,
     880                substr( $value, $prefix_len )
     881            );
     882            $value          = "var(--wp--$unwrapped_name)";
     883        }
     884
     885        return $value;
     886    }
     887
     888    /**
    213889     * Builds metadata for the setting nodes, which returns in the form of:
    214890     *
    215      *     [
    216      *       [
    217      *         'path' => ['path', 'to', 'some', 'node' ]
    218      *       ],
    219      *       [
    220      *         'path' => [ 'path', 'to', 'other', 'node' ]
    221      *       ],
    222      *     ]
     891     * [
     892     *   [
     893     *     'path'     => ['path', 'to', 'some', 'node' ],
     894     *     'selector' => 'CSS selector for some node'
     895     *   ],
     896     *   [
     897     *     'path'     => [ 'path', 'to', 'other', 'node' ],
     898     *     'selector' => 'CSS selector for other node'
     899     *   ],
     900     * ]
    223901     *
    224902     * @since 5.8.0
    225903     *
    226904     * @param array $theme_json The tree to extract setting nodes from.
     905     * @param array $selectors List of selectors per block.
     906     *
    227907     * @return array
    228908     */
    229     private static function get_setting_nodes( $theme_json ) {
     909    private static function get_setting_nodes( $theme_json, $selectors = array() ) {
    230910        $nodes = array();
    231911        if ( ! isset( $theme_json['settings'] ) ) {
     
    235915        // Top-level.
    236916        $nodes[] = array(
    237             'path' => array( 'settings' ),
     917            'path'     => array( 'settings' ),
     918            'selector' => self::ROOT_BLOCK_SELECTOR,
    238919        );
    239920
     
    244925
    245926        foreach ( $theme_json['settings']['blocks'] as $name => $node ) {
     927            $selector = null;
     928            if ( isset( $selectors[ $name ]['selector'] ) ) {
     929                $selector = $selectors[ $name ]['selector'];
     930            }
     931
    246932            $nodes[] = array(
    247                 'path' => array( 'settings', 'blocks', $name ),
     933                'path'     => array( 'settings', 'blocks', $name ),
     934                'selector' => $selector,
    248935            );
     936        }
     937
     938        return $nodes;
     939    }
     940
     941
     942    /**
     943     * Builds metadata for the style nodes, which returns in the form of:
     944     *
     945     * [
     946     *   [
     947     *     'path'     => [ 'path', 'to', 'some', 'node' ],
     948     *     'selector' => 'CSS selector for some node'
     949     *   ],
     950     *   [
     951     *     'path'     => ['path', 'to', 'other', 'node' ],
     952     *     'selector' => 'CSS selector for other node'
     953     *   ],
     954     * ]
     955     *
     956     * @since 5.8.0
     957     *
     958     * @param array $theme_json The tree to extract style nodes from.
     959     * @param array $selectors List of selectors per block.
     960     *
     961     * @return array
     962     */
     963    private static function get_style_nodes( $theme_json, $selectors = array() ) {
     964        $nodes = array();
     965        if ( ! isset( $theme_json['styles'] ) ) {
     966            return $nodes;
     967        }
     968
     969        // Top-level.
     970        $nodes[] = array(
     971            'path'     => array( 'styles' ),
     972            'selector' => self::ROOT_BLOCK_SELECTOR,
     973        );
     974
     975        if ( isset( $theme_json['styles']['elements'] ) ) {
     976            foreach ( $theme_json['styles']['elements'] as $element => $node ) {
     977                $nodes[] = array(
     978                    'path'     => array( 'styles', 'elements', $element ),
     979                    'selector' => self::ELEMENTS[ $element ],
     980                );
     981            }
     982        }
     983
     984        // Blocks.
     985        if ( ! isset( $theme_json['styles']['blocks'] ) ) {
     986            return $nodes;
     987        }
     988
     989        foreach ( $theme_json['styles']['blocks'] as $name => $node ) {
     990            $selector = null;
     991            if ( isset( $selectors[ $name ]['selector'] ) ) {
     992                $selector = $selectors[ $name ]['selector'];
     993            }
     994
     995            $nodes[] = array(
     996                'path'     => array( 'styles', 'blocks', $name ),
     997                'selector' => $selector,
     998            );
     999
     1000            if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
     1001                foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) {
     1002                    $nodes[] = array(
     1003                        'path'     => array( 'styles', 'blocks', $name, 'elements', $element ),
     1004                        'selector' => $selectors[ $name ]['elements'][ $element ],
     1005                    );
     1006                }
     1007            }
    2491008        }
    2501009
  • trunk/src/wp-includes/default-filters.php

    r50836 r50973  
    536536add_action( 'wp_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 );
    537537add_action( 'wp_enqueue_scripts', 'wp_common_block_scripts_and_styles' );
     538add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' );
    538539add_action( 'admin_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 );
    539540add_action( 'admin_enqueue_scripts', 'wp_common_block_scripts_and_styles' );
  • trunk/src/wp-includes/script-loader.php

    r50934 r50973  
    22342234
    22352235/**
     2236 * Enqueues the global styles defined via theme.json.
     2237 *
     2238 * @since 5.8.0
     2239 *
     2240 * @return void
     2241 */
     2242function wp_enqueue_global_styles() {
     2243    if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
     2244        return;
     2245    }
     2246
     2247    $can_use_cache = (
     2248        ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
     2249        ( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
     2250        ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
     2251        ! is_admin()
     2252    );
     2253
     2254    $stylesheet = null;
     2255    if ( $can_use_cache ) {
     2256        $cache = get_transient( 'global_styles' );
     2257        if ( $cache ) {
     2258            $stylesheet = $cache;
     2259        }
     2260    }
     2261
     2262    if ( null === $stylesheet ) {
     2263        $settings   = get_default_block_editor_settings();
     2264        $theme_json = WP_Theme_JSON_Resolver::get_merged_data( $settings );
     2265        $stylesheet = $theme_json->get_stylesheet();
     2266
     2267        if ( $can_use_cache ) {
     2268            set_transient( 'global_styles', $stylesheet, MINUTE_IN_SECONDS );
     2269        }
     2270    }
     2271
     2272    if ( empty( $stylesheet ) ) {
     2273        return;
     2274    }
     2275
     2276    wp_register_style( 'global-styles', false, array(), true, true );
     2277    wp_add_inline_style( 'global-styles', $stylesheet );
     2278    wp_enqueue_style( 'global-styles' );
     2279}
     2280
     2281/**
    22362282 * Checks if the editor scripts and styles for all registered block types
    22372283 * should be enqueued on the current screen.
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r50967 r50973  
    3535                ),
    3636                'styles'   => array(
    37                     'color' => array(
    38                         'link' => 'blue',
     37                    'elements' => array(
     38                        'link' => array(
     39                            'color' => array(
     40                                'text' => '#111',
     41                            ),
     42                        ),
    3943                    ),
    4044                ),
     
    5862
    5963        $this->assertEqualSetsWithIndex( $expected, $actual );
     64    }
     65
     66    function test_get_stylesheet() {
     67        $theme_json = new WP_Theme_JSON(
     68            array(
     69                'version'  => WP_Theme_JSON::LATEST_SCHEMA,
     70                'settings' => array(
     71                    'color'  => array(
     72                        'text'    => 'value',
     73                        'palette' => array(
     74                            array(
     75                                'slug'  => 'grey',
     76                                'color' => 'grey',
     77                            ),
     78                        ),
     79                    ),
     80                    'misc'   => 'value',
     81                    'blocks' => array(
     82                        'core/group' => array(
     83                            'custom' => array(
     84                                'base-font'   => 16,
     85                                'line-height' => array(
     86                                    'small'  => 1.2,
     87                                    'medium' => 1.4,
     88                                    'large'  => 1.8,
     89                                ),
     90                            ),
     91                        ),
     92                    ),
     93                ),
     94                'styles'   => array(
     95                    'color'    => array(
     96                        'text' => 'var:preset|color|grey',
     97                    ),
     98                    'misc'     => 'value',
     99                    'elements' => array(
     100                        'link' => array(
     101                            'color' => array(
     102                                'text'       => '#111',
     103                                'background' => '#333',
     104                            ),
     105                        ),
     106                    ),
     107                    'blocks'   => array(
     108                        'core/group'     => array(
     109                            'elements' => array(
     110                                'link' => array(
     111                                    'color' => array(
     112                                        'text' => '#111',
     113                                    ),
     114                                ),
     115                            ),
     116                            'spacing'  => array(
     117                                'padding' => array(
     118                                    'top'    => '12px',
     119                                    'bottom' => '24px',
     120                                ),
     121                            ),
     122                        ),
     123                        'core/heading'   => array(
     124                            'color'    => array(
     125                                'text' => '#123456',
     126                            ),
     127                            'elements' => array(
     128                                'link' => array(
     129                                    'color'      => array(
     130                                        'text'       => '#111',
     131                                        'background' => '#333',
     132                                    ),
     133                                    'typography' => array(
     134                                        'fontSize' => '60px',
     135                                    ),
     136                                ),
     137                            ),
     138                        ),
     139                        'core/post-date' => array(
     140                            'color'    => array(
     141                                'text' => '#123456',
     142                            ),
     143                            'elements' => array(
     144                                'link' => array(
     145                                    'color' => array(
     146                                        'background' => '#777',
     147                                        'text'       => '#555',
     148                                    ),
     149                                ),
     150                            ),
     151                        ),
     152                    ),
     153                ),
     154                'misc'     => 'value',
     155            )
     156        );
     157
     158        $this->assertEquals(
     159            'body{--wp--preset--color--grey: grey;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}body{color: var(--wp--preset--color--grey);}a{background-color: #333;color: #111;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group a{color: #111;}h1,h2,h3,h4,h5,h6{color: #123456;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a{background-color: #777;color: #555;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}',
     160            $theme_json->get_stylesheet()
     161        );
     162        $this->assertEquals(
     163            'body{color: var(--wp--preset--color--grey);}a{background-color: #333;color: #111;}.wp-block-group{padding-top: 12px;padding-bottom: 24px;}.wp-block-group a{color: #111;}h1,h2,h3,h4,h5,h6{color: #123456;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{background-color: #333;color: #111;font-size: 60px;}.wp-block-post-date{color: #123456;}.wp-block-post-date a{background-color: #777;color: #555;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}',
     164            $theme_json->get_stylesheet( 'block_styles' )
     165        );
     166        $this->assertEquals(
     167            'body{--wp--preset--color--grey: grey;}.wp-block-group{--wp--custom--base-font: 16;--wp--custom--line-height--small: 1.2;--wp--custom--line-height--medium: 1.4;--wp--custom--line-height--large: 1.8;}',
     168            $theme_json->get_stylesheet( 'css_variables' )
     169        );
     170    }
     171
     172    function test_get_stylesheet_preset_rules_come_after_block_rules() {
     173        $theme_json = new WP_Theme_JSON(
     174            array(
     175                'version'  => WP_Theme_JSON::LATEST_SCHEMA,
     176                'settings' => array(
     177                    'blocks' => array(
     178                        'core/group' => array(
     179                            'color' => array(
     180                                'palette' => array(
     181                                    array(
     182                                        'slug'  => 'grey',
     183                                        'color' => 'grey',
     184                                    ),
     185                                ),
     186                            ),
     187                        ),
     188                    ),
     189                ),
     190                'styles'   => array(
     191                    'blocks' => array(
     192                        'core/group' => array(
     193                            'color' => array(
     194                                'text' => 'red',
     195                            ),
     196                        ),
     197                    ),
     198                ),
     199            )
     200        );
     201
     202        $this->assertEquals(
     203            '.wp-block-group{--wp--preset--color--grey: grey;}.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: grey !important;}.wp-block-group.has-grey-background-color{background-color: grey !important;}',
     204            $theme_json->get_stylesheet()
     205        );
     206        $this->assertEquals(
     207            '.wp-block-group{color: red;}.wp-block-group.has-grey-color{color: grey !important;}.wp-block-group.has-grey-background-color{background-color: grey !important;}',
     208            $theme_json->get_stylesheet( 'block_styles' )
     209        );
     210    }
     211
     212    public function test_get_stylesheet_preset_values_are_marked_as_important() {
     213        $theme_json = new WP_Theme_JSON(
     214            array(
     215                'version'  => WP_Theme_JSON::LATEST_SCHEMA,
     216                'settings' => array(
     217                    'color' => array(
     218                        'palette' => array(
     219                            array(
     220                                'slug'  => 'grey',
     221                                'color' => 'grey',
     222                            ),
     223                        ),
     224                    ),
     225                ),
     226                'styles'   => array(
     227                    'blocks' => array(
     228                        'core/paragraph' => array(
     229                            'color'      => array(
     230                                'text'       => 'red',
     231                                'background' => 'blue',
     232                            ),
     233                            'typography' => array(
     234                                'fontSize'   => '12px',
     235                                'lineHeight' => '1.3',
     236                            ),
     237                        ),
     238                    ),
     239                ),
     240            )
     241        );
     242
     243        $this->assertEquals(
     244            'body{--wp--preset--color--grey: grey;}p{background-color: blue;color: red;font-size: 12px;line-height: 1.3;}.has-grey-color{color: grey !important;}.has-grey-background-color{background-color: grey !important;}',
     245            $theme_json->get_stylesheet()
     246        );
    60247    }
    61248
     
    251438                        'color' => array(
    252439                            'custom' => false,
     440                        ),
     441                    ),
     442                ),
     443            ),
     444            'styles'   => array(
     445                'typography' => array(
     446                    'fontSize' => '12',
     447                ),
     448                'blocks'     => array(
     449                    'core/group' => array(
     450                        'spacing' => array(
     451                            'padding' => array(
     452                                'top'    => '12px',
     453                                'bottom' => '12px',
     454                            ),
     455                        ),
     456                    ),
     457                    'core/list'  => array(
     458                        'typography' => array(
     459                            'fontSize' => '12',
     460                        ),
     461                        'color'      => array(
     462                            'background' => 'brown',
    253463                        ),
    254464                    ),
Note: See TracChangeset for help on using the changeset viewer.