Make WordPress Core

Changeset 61473


Ignore:
Timestamp:
01/12/2026 12:18:12 PM (5 weeks ago)
Author:
youknowriad
Message:

Global Styles: Lift classic block restrictions.

Enable Global Styles functionality in classic WordPress themes, allowing features like the Font Library to work without requiring a theme.json file.

This change:

  • Removes restrictions that prevented classic themes from accessing Global Styles features.
  • Enables font functionality in classic themes through the Font Library.
  • Fixes Fonts menu not appearing in classic themes by changing its submenu index to avoid collision with Widgets.

Props youknowriad, isabel_brison, ramonopoly.
Fixes #64408.

Location:
trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/menu.php

    r61438 r61473  
    238238
    239239// Font Library menu item.
    240 $submenu['themes.php'][8] = array( __( 'Fonts' ), 'edit_theme_options', 'font-library.php' );
     240$submenu['themes.php'][9] = array( __( 'Fonts' ), 'edit_theme_options', 'font-library.php' );
    241241
    242242$customize_url = add_query_arg( 'return', urlencode( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ), 'customize.php' );
  • trunk/src/wp-includes/block-editor.php

    r61431 r61473  
    526526    }
    527527
    528     if ( wp_theme_has_theme_json() ) {
    529         $block_classes = array(
    530             'css'            => 'styles',
    531             '__unstableType' => 'theme',
    532             'isGlobalStyles' => true,
    533         );
    534         $actual_css    = wp_get_global_stylesheet( array( $block_classes['css'] ) );
    535         if ( '' !== $actual_css ) {
    536             $block_classes['css'] = $actual_css;
    537             $global_styles[]      = $block_classes;
    538         }
    539 
    540         /*
    541          * Add the custom CSS as a separate stylesheet so any invalid CSS
    542          * entered by users does not break other global styles.
    543          */
    544         $global_styles[] = array(
    545             'css'            => wp_get_global_stylesheet( array( 'custom-css' ) ),
    546             '__unstableType' => 'user',
    547             'isGlobalStyles' => true,
    548         );
    549     } else {
    550         // If there is no `theme.json` file, ensure base layout styles are still available.
    551         $block_classes = array(
    552             'css'            => 'base-layout-styles',
    553             '__unstableType' => 'base-layout',
    554             'isGlobalStyles' => true,
    555         );
    556         $actual_css    = wp_get_global_stylesheet( array( $block_classes['css'] ) );
    557         if ( '' !== $actual_css ) {
    558             $block_classes['css'] = $actual_css;
    559             $global_styles[]      = $block_classes;
    560         }
    561     }
     528    $block_classes = array(
     529        'css'            => 'styles',
     530        '__unstableType' => 'theme',
     531        'isGlobalStyles' => true,
     532    );
     533    $actual_css    = wp_get_global_stylesheet( array( $block_classes['css'] ) );
     534    if ( '' !== $actual_css ) {
     535        $block_classes['css'] = $actual_css;
     536        $global_styles[]      = $block_classes;
     537    }
     538
     539    // Get any additional css from the customizer and add it before global styles custom CSS.
     540    $global_styles[] = array(
     541        'css'            => wp_get_custom_css(),
     542        '__unstableType' => 'user',
     543        'isGlobalStyles' => false,
     544    );
     545
     546    /*
     547     * Add the custom CSS as a separate stylesheet so any invalid CSS
     548     * entered by users does not break other global styles.
     549     */
     550    $global_styles[] = array(
     551        'css'            => wp_get_global_stylesheet( array( 'custom-css' ) ),
     552        '__unstableType' => 'user',
     553        'isGlobalStyles' => true,
     554    );
    562555
    563556    $editor_settings['styles'] = array_merge( $global_styles, get_block_editor_theme_styles() );
  • trunk/src/wp-includes/class-wp-theme-json-resolver.php

    r61400 r61473  
    481481        }
    482482
    483         /*
    484          * Bail early if the theme does not support a theme.json.
    485          *
    486          * Since wp_theme_has_theme_json() only supports the active
    487          * theme, the extra condition for whether $theme is the active theme is
    488          * present here.
    489          */
    490         if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) {
    491             return array();
    492         }
    493 
    494483        $user_cpt         = array();
    495484        $post_type_filter = 'wp_global_styles';
  • trunk/src/wp-includes/class-wp-theme-json.php

    r61431 r61473  
    13271327     * @since 6.6.0 Added boolean `skip_root_layout_styles` and `include_block_style_variations` options
    13281328     *              to control styles output as desired.
     1329     * @since 7.0.0 Deprecated 'base-layout-styles' type; added `base_layout_styles` option for classic themes.
    13291330     *
    13301331     * @param string[] $types   Types of styles to load. Will load all by default. It accepts:
     
    13321333     *                          - `styles`: only the styles section in theme.json.
    13331334     *                          - `presets`: only the classes for the presets.
    1334      *                          - `base-layout-styles`: only the base layout styles.
     1335     *                          - `base-layout-styles`: only the base layout styles. Deprecated in 7.0.0.
    13351336     *                          - `custom-css`: only the custom CSS.
    13361337     * @param string[] $origins A list of origins to include. By default it includes VALID_ORIGINS.
     
    13411342     *     @type string $root_selector                   Overwrites and forces a given selector to be used on the root node
    13421343     *     @type bool   $skip_root_layout_styles         Omits root layout styles from the generated stylesheet. Default false.
     1344     *     @type bool   $base_layout_styles              When true generates only base layout styles without alignment rules. Default false.
    13431345     *     @type bool   $include_block_style_variations  Includes styles for block style variations in the generated stylesheet. Default false.
    13441346     * }
     
    13961398        if ( in_array( 'styles', $types, true ) ) {
    13971399            if ( false !== $root_style_key && empty( $options['skip_root_layout_styles'] ) ) {
    1398                 $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] );
     1400                $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ], $options );
    13991401            }
    14001402            $stylesheet .= $this->get_block_classes( $style_nodes );
    1401         } elseif ( in_array( 'base-layout-styles', $types, true ) ) {
    1402             $root_selector          = static::ROOT_BLOCK_SELECTOR;
    1403             $columns_selector       = '.wp-block-columns';
    1404             $post_template_selector = '.wp-block-post-template';
    1405             if ( ! empty( $options['scope'] ) ) {
    1406                 $root_selector          = static::scope_selector( $options['scope'], $root_selector );
    1407                 $columns_selector       = static::scope_selector( $options['scope'], $columns_selector );
    1408                 $post_template_selector = static::scope_selector( $options['scope'], $post_template_selector );
    1409             }
    1410             if ( ! empty( $options['root_selector'] ) ) {
    1411                 $root_selector = $options['root_selector'];
    1412             }
    1413             /*
    1414              * Base layout styles are provided as part of `styles`, so only output separately if explicitly requested.
    1415              * For backwards compatibility, the Columns block is explicitly included, to support a different default gap value.
    1416              */
    1417             $base_styles_nodes = array(
    1418                 array(
    1419                     'path'     => array( 'styles' ),
    1420                     'selector' => $root_selector,
    1421                 ),
    1422                 array(
    1423                     'path'     => array( 'styles', 'blocks', 'core/columns' ),
    1424                     'selector' => $columns_selector,
    1425                     'name'     => 'core/columns',
    1426                 ),
    1427                 array(
    1428                     'path'     => array( 'styles', 'blocks', 'core/post-template' ),
    1429                     'selector' => $post_template_selector,
    1430                     'name'     => 'core/post-template',
    1431                 ),
    1432             );
    1433 
    1434             foreach ( $base_styles_nodes as $base_style_node ) {
    1435                 $stylesheet .= $this->get_layout_styles( $base_style_node, $types );
    1436             }
    14371403        }
    14381404
     
    16251591     * @since 6.5.3 Add types parameter to check if only base layout styles are needed.
    16261592     * @since 6.6.0 Updated layout style specificity to be compatible with overall 0-1-0 specificity in global styles.
     1593     * @since 7.0.0 Replaced `$types` parameter with `$options` array; base layout styles controlled via `base_layout_styles` option.
    16271594     *
    16281595     * @param array $block_metadata Metadata about the block to get styles for.
    1629      * @param array $types          Optional. Types of styles to output. If empty, all styles will be output.
     1596     * @param array $options        Optional. An array of options for now used for internal purposes only.
    16301597     * @return string Layout styles for the block.
    16311598     */
    1632     protected function get_layout_styles( $block_metadata, $types = array() ) {
     1599    protected function get_layout_styles( $block_metadata, $options = array() ) {
    16331600        $block_rules = '';
    16341601        $block_type  = null;
     
    17781745                        $declarations = array();
    17791746
    1780                         // Skip outputting base styles for flow and constrained layout types if theme doesn't support theme.json. The 'base-layout-styles' type flags this.
    1781                         if ( in_array( 'base-layout-styles', $types, true ) && ( 'default' === $layout_definition['name'] || 'constrained' === $layout_definition['name'] ) ) {
     1747                        // Skip outputting base styles for flow and constrained layout types when base_layout_styles is enabled.
     1748                        // These themes don't use .wp-site-blocks wrapper, so these layout-specific alignment styles aren't needed.
     1749                        if ( ! empty( $options['base_layout_styles'] ) && ( 'default' === $layout_definition['name'] || 'constrained' === $layout_definition['name'] ) ) {
    17821750                            continue;
    17831751                        }
     
    30563024     * @since 6.6.0 Use `ROOT_CSS_PROPERTIES_SELECTOR` for CSS custom properties and improved consistency of root padding rules.
    30573025     *              Updated specificity of body margin reset and first/last child selectors.
     3026     * @since 7.0.0 Added `$options` parameter to control alignment styles output for classic themes.
    30583027     *
    30593028     * @param string $selector The root node selector.
    30603029     * @param array  $block_metadata The metadata for the root block.
     3030     * @param array  $options        Optional. An array of options for now used for internal purposes only.
    30613031     * @return string The additional root rules CSS.
    30623032     */
    3063     public function get_root_layout_rules( $selector, $block_metadata ) {
     3033    public function get_root_layout_rules( $selector, $block_metadata, $options = array() ) {
    30643034        $css              = '';
    30653035        $settings         = $this->theme_json['settings'] ?? array();
     
    31023072        }
    31033073
    3104         $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
    3105         $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
    3106         $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
     3074        // Skip outputting alignment styles when base_layout_styles is enabled.
     3075        // These styles target .wp-site-blocks which is only used by block themes.
     3076        if ( empty( $options['base_layout_styles'] ) ) {
     3077            $css .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }';
     3078            $css .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }';
     3079            $css .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }';
     3080        }
    31073081
    31083082        // Block gap styles will be output unless explicitly set to `null`. See static::PROTECTED_PROPERTIES.
     
    31163090            $css .= static::ROOT_CSS_PROPERTIES_SELECTOR . " { --wp--style--block-gap: $block_gap_value; }";
    31173091        }
    3118         $css .= $this->get_layout_styles( $block_metadata );
     3092        $css .= $this->get_layout_styles( $block_metadata, $options );
    31193093
    31203094        return $css;
  • trunk/src/wp-includes/global-styles-and-settings.php

    r61470 r61473  
    4040     */
    4141    $origin = 'custom';
    42     if (
    43         ! wp_theme_has_theme_json() ||
    44         ( isset( $context['origin'] ) && 'base' === $context['origin'] )
    45     ) {
     42    if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) {
    4643        $origin = 'theme';
    4744    }
     
    141138 * @since 6.1.0 Added 'base-layout-styles' support.
    142139 * @since 6.6.0 Resolves relative paths in theme.json styles to theme absolute paths.
     140 * @since 7.0.0 Deprecated 'base-layout-styles' type; classic themes now receive full styles
     141 *              with layout-specific alignment rules skipped via `base_layout_styles` option.
    143142 *
    144143 * @param array $types Optional. Types of styles to load.
    145144 *                     See {@see 'WP_Theme_JSON::get_stylesheet'} for all valid types.
    146  *                     If empty, it'll load the following:
    147  *                     - for themes without theme.json: 'variables', 'presets', 'base-layout-styles'.
    148  *                     - for themes with theme.json: 'variables', 'presets', 'styles'.
     145 *                     If empty, will load: 'variables', 'presets', 'styles'.
    149146 * @return string Stylesheet.
    150147 */
     
    181178    }
    182179
    183     $tree                = WP_Theme_JSON_Resolver::resolve_theme_file_uris( WP_Theme_JSON_Resolver::get_merged_data() );
    184     $supports_theme_json = wp_theme_has_theme_json();
    185 
    186     if ( empty( $types ) && ! $supports_theme_json ) {
    187         $types = array( 'variables', 'presets', 'base-layout-styles' );
    188     } elseif ( empty( $types ) ) {
     180    $tree = WP_Theme_JSON_Resolver::resolve_theme_file_uris( WP_Theme_JSON_Resolver::get_merged_data() );
     181
     182    if ( empty( $types ) ) {
    189183        $types = array( 'variables', 'styles', 'presets' );
     184    }
     185
     186    /*
     187     * Enable base layout styles only mode for classic themes without theme.json.
     188     * This skips alignment styles that target .wp-site-blocks which is only used by block themes.
     189     */
     190    $options = array();
     191    if ( ! wp_is_block_theme() && ! wp_theme_has_theme_json() ) {
     192        $options['base_layout_styles'] = true;
    190193    }
    191194
     
    205208         */
    206209        $origins          = array( 'default', 'theme', 'custom' );
    207         $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins );
     210        $styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins, $options );
    208211        $types            = array_diff( $types, array( 'variables' ) );
    209212    }
     
    223226         * @see wp_add_global_styles_for_blocks
    224227         */
    225         $origins = array( 'default', 'theme', 'custom' );
    226         /*
    227          * If the theme doesn't have theme.json but supports both appearance tools and color palette,
    228          * the 'theme' origin should be included so color palette presets are also output.
    229          */
    230         if ( ! $supports_theme_json && ( current_theme_supports( 'appearance-tools' ) || current_theme_supports( 'border' ) ) && current_theme_supports( 'editor-color-palette' ) ) {
    231             $origins = array( 'default', 'theme' );
    232         } elseif ( ! $supports_theme_json ) {
    233             $origins = array( 'default' );
    234         }
    235         $styles_rest = $tree->get_stylesheet( $types, $origins );
     228        $origins     = array( 'default', 'theme', 'custom' );
     229        $styles_rest = $tree->get_stylesheet( $types, $origins, $options );
    236230    }
    237231
  • trunk/src/wp-includes/script-loader.php

    r61469 r61473  
    25152515    $stylesheet = wp_get_global_stylesheet();
    25162516
    2517     if ( $is_block_theme ) {
    2518         /*
    2519          * Dequeue the Customizer's custom CSS
    2520          * and add it before the global styles custom CSS.
    2521          */
    2522         remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
    2523 
    2524         /*
    2525          * Get the custom CSS from the Customizer and add it to the global stylesheet.
    2526          * Always do this in Customizer preview for the sake of live preview since it be empty.
    2527          */
    2528         $custom_css = trim( wp_get_custom_css() );
    2529         if ( $custom_css || is_customize_preview() ) {
    2530             if ( is_customize_preview() ) {
    2531                 /*
    2532                  * When in the Customizer preview, wrap the Custom CSS in milestone comments to allow customize-preview.js
    2533                  * to locate the CSS to replace for live previewing. Make sure that the milestone comments are omitted from
    2534                  * the stored Custom CSS if by chance someone tried to add them, which would be highly unlikely, but it
    2535                  * would break live previewing.
    2536                  */
    2537                 $before_milestone = '/*BEGIN_CUSTOMIZER_CUSTOM_CSS*/';
    2538                 $after_milestone  = '/*END_CUSTOMIZER_CUSTOM_CSS*/';
    2539                 $custom_css       = str_replace( array( $before_milestone, $after_milestone ), '', $custom_css );
    2540                 $custom_css       = $before_milestone . "\n" . $custom_css . "\n" . $after_milestone;
    2541             }
    2542             $custom_css = "\n" . $custom_css;
    2543         }
    2544         $stylesheet .= $custom_css;
    2545 
    2546         // Add the global styles custom CSS at the end.
    2547         $stylesheet .= wp_get_global_stylesheet( array( 'custom-css' ) );
    2548     }
     2517    /*
     2518     * Dequeue the Customizer's custom CSS
     2519     * and add it before the global styles custom CSS.
     2520     */
     2521    remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
     2522
     2523    /*
     2524     * Get the custom CSS from the Customizer and add it to the global stylesheet.
     2525     * Always do this in Customizer preview for the sake of live preview since it be empty.
     2526     */
     2527    $custom_css = trim( wp_get_custom_css() );
     2528    if ( $custom_css || is_customize_preview() ) {
     2529        if ( is_customize_preview() ) {
     2530            /*
     2531             * When in the Customizer preview, wrap the Custom CSS in milestone comments to allow customize-preview.js
     2532             * to locate the CSS to replace for live previewing. Make sure that the milestone comments are omitted from
     2533             * the stored Custom CSS if by chance someone tried to add them, which would be highly unlikely, but it
     2534             * would break live previewing.
     2535             */
     2536            $before_milestone = '/*BEGIN_CUSTOMIZER_CUSTOM_CSS*/';
     2537            $after_milestone  = '/*END_CUSTOMIZER_CUSTOM_CSS*/';
     2538            $custom_css       = str_replace( array( $before_milestone, $after_milestone ), '', $custom_css );
     2539            $custom_css       = $before_milestone . "\n" . $custom_css . "\n" . $after_milestone;
     2540        }
     2541        $custom_css = "\n" . $custom_css;
     2542    }
     2543    $stylesheet .= $custom_css;
     2544
     2545    // Add the global styles custom CSS at the end.
     2546    $stylesheet .= wp_get_global_stylesheet( array( 'custom-css' ) );
    25492547
    25502548    if ( empty( $stylesheet ) ) {
  • trunk/tests/phpunit/tests/template.php

    r61469 r61473  
    15621562                        'normal-css',
    15631563                        'normal-inline-css',
    1564                         'wp-custom-css',
    15651564                    ),
    15661565                    'BODY' => array(
     
    16241623                        'normal-css',
    16251624                        'normal-inline-css',
    1626                         'wp-custom-css',
    16271625                    ),
    16281626                    'BODY' => array(
  • trunk/tests/phpunit/tests/theme/wpThemeJson.php

    r61190 r61473  
    12171217                    ),
    12181218                ),
     1219                'styles'   => array(
     1220                    'spacing' => array(
     1221                        'blockGap' => '1em',
     1222                    ),
     1223                ),
    12191224            ),
    12201225            'default'
    12211226        );
    1222         $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) );
    1223 
    1224         // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility.
     1227        // Set base_layout_styles to true to generate only base layout styles without alignment rules.
     1228        $stylesheet = $theme_json->get_stylesheet( array( 'styles' ), null, array( 'base_layout_styles' => true ) );
     1229
     1230        // Verify that layout styles are still generated, but without .wp-site-blocks alignment rules and flow/constrained base styles.
    12251231        $this->assertSame(
    1226             ':where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;}',
     1232            ':where(body) { margin: 0; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}',
    12271233            $stylesheet
    12281234        );
     
    12461252            'default'
    12471253        );
    1248         $stylesheet = $theme_json->get_stylesheet( array( 'base-layout-styles' ) );
     1254        $stylesheet = $theme_json->get_stylesheet( array( 'styles' ), null );
    12491255        remove_theme_support( 'disable-layout-styles' );
    12501256
    1251         // All Layout styles should be skipped.
     1257        // All Layout styles should be skipped when disable-layout-styles theme support is added.
    12521258        $this->assertSame(
    12531259            '',
  • trunk/tests/phpunit/tests/theme/wpThemeJsonResolver.php

    r60729 r61473  
    735735     * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles
    736736     */
    737     public function test_get_user_data_from_wp_global_styles_does_not_run_for_theme_without_support() {
    738         // The 'default' theme does not support theme.json.
     737    public function test_get_user_data_from_wp_global_styles_runs_for_classic_themes() {
     738        // The 'default' theme does not support theme.json (classic theme).
    739739        switch_theme( 'default' );
    740740        wp_set_current_user( self::$administrator_id );
    741741        $theme = wp_get_theme();
    742742
    743         $start_queries = get_num_queries();
    744 
    745         // When theme.json is not supported, the method should not run a query and always return an empty result.
    746         $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
    747         $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' );
    748         $this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' );
    749 
     743        // Classic themes should now be able to access user global styles data.
     744        // When should_create_post is true, it should create a post.
    750745        $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true );
    751         $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' );
    752         $this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' );
     746        $this->assertIsArray( $user_cpt, 'User CPT should be an array for classic themes.' );
     747        $this->assertArrayHasKey( 'ID', $user_cpt, 'User CPT should have an ID for classic themes.' );
     748
     749        // Clean up the created post.
     750        if ( isset( $user_cpt['ID'] ) ) {
     751            wp_delete_post( $user_cpt['ID'], true );
     752        }
    753753    }
    754754
Note: See TracChangeset for help on using the changeset viewer.