Make WordPress Core

Changeset 50973


Ignore:
Timestamp:
05/24/2021 05:38:59 PM (5 years 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.