From 5b9f383603ffbcca80159451be9b11465728c4b4 Mon Sep 17 00:00:00 2001 From: ellatrix Date: Tue, 4 Jun 2024 11:55:14 +0000 Subject: [PATCH] Editor: Add theme.json v3 migrations. See https://github.com/WordPress/wordpress-develop/pull/6616. See also the original Gutenberg PRs: * https://github.com/WordPress/gutenberg/pull/58409 * https://github.com/WordPress/gutenberg/pull/61328 * https://github.com/WordPress/gutenberg/pull/61842 * https://github.com/WordPress/gutenberg/pull/62199 * https://github.com/WordPress/gutenberg/pull/62252 Fixes #61282. Props ajlende, talldanwp, ramonopoly, ellatrix. Built from https://develop.svn.wordpress.org/trunk@58328 git-svn-id: http://core.svn.wordpress.org/trunk@57785 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/block-editor.php | 6 + wp-includes/class-wp-theme-json-data.php | 2 +- wp-includes/class-wp-theme-json-resolver.php | 45 ++- wp-includes/class-wp-theme-json-schema.php | 93 +++++- wp-includes/class-wp-theme-json.php | 268 ++++++++++++++---- .../class-wp-rest-font-faces-controller.php | 2 +- ...class-wp-rest-font-families-controller.php | 2 +- wp-includes/theme.json | 4 +- wp-includes/theme.php | 27 ++ wp-includes/version.php | 2 +- 10 files changed, 384 insertions(+), 67 deletions(-) diff --git a/wp-includes/block-editor.php b/wp-includes/block-editor.php index e04b012e7dd..fdc4846f69f 100644 --- a/wp-includes/block-editor.php +++ b/wp-includes/block-editor.php @@ -814,6 +814,7 @@ function get_block_editor_theme_styles() { * Returns the classic theme supports settings for block editor. * * @since 6.2.0 + * @since 6.6.0 Add support for 'editor-spacing-sizes' theme support. * * @return array The classic theme supports settings. */ @@ -844,5 +845,10 @@ function get_classic_theme_supports_block_editor_settings() { $theme_settings['gradients'] = $gradient_presets; } + $spacing_sizes = current( (array) get_theme_support( 'editor-spacing-sizes' ) ); + if ( false !== $spacing_sizes ) { + $theme_settings['spacingSizes'] = $spacing_sizes; + } + return $theme_settings; } diff --git a/wp-includes/class-wp-theme-json-data.php b/wp-includes/class-wp-theme-json-data.php index 30dab47185c..ca72ae81b5a 100644 --- a/wp-includes/class-wp-theme-json-data.php +++ b/wp-includes/class-wp-theme-json-data.php @@ -39,7 +39,7 @@ class WP_Theme_JSON_Data { * @param array $data Array following the theme.json specification. * @param string $origin The origin of the data: default, theme, user. */ - public function __construct( $data = array(), $origin = 'theme' ) { + public function __construct( $data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ), $origin = 'theme' ) { $this->origin = $origin; $this->theme_json = new WP_Theme_JSON( $data, $this->origin ); } diff --git a/wp-includes/class-wp-theme-json-resolver.php b/wp-includes/class-wp-theme-json-resolver.php index 0bdf4a23db1..c3a5db45ad7 100644 --- a/wp-includes/class-wp-theme-json-resolver.php +++ b/wp-includes/class-wp-theme-json-resolver.php @@ -220,6 +220,7 @@ protected static function has_same_registered_blocks( $origin ) { * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. + * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. * * @param array $deprecated Deprecated. Not used. * @param array $options { @@ -243,7 +244,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_json_data = static::read_json_file( $theme_json_file ); $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); } else { - $theme_json_data = array(); + $theme_json_data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); } /** @@ -310,6 +311,32 @@ public static function get_theme_data( $deprecated = array(), $options = array() } $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; + if ( ! isset( $theme_support_data['settings']['typography'] ) ) { + $theme_support_data['settings']['typography'] = array(); + } + $default_font_sizes = false; + if ( current_theme_supports( 'default-font-sizes' ) ) { + $default_font_sizes = true; + } + if ( ! isset( $theme_support_data['settings']['typography']['fontSizes'] ) ) { + // If the theme does not have any font sizes, we still want to show the core one. + $default_font_sizes = true; + } + $theme_support_data['settings']['typography']['defaultFontSizes'] = $default_font_sizes; + + if ( ! isset( $theme_support_data['settings']['spacing'] ) ) { + $theme_support_data['settings']['spacing'] = array(); + } + $default_spacing_sizes = false; + if ( current_theme_supports( 'default-spacing-sizes' ) ) { + $default_spacing_sizes = true; + } + if ( ! isset( $theme_support_data['settings']['spacing']['spacingSizes'] ) ) { + // If the theme does not have any spacing sizes, we still want to show the core one. + $default_spacing_sizes = true; + } + $theme_support_data['settings']['spacing']['defaultSpacingSizes'] = $default_spacing_sizes; + if ( ! isset( $theme_support_data['settings']['shadow'] ) ) { $theme_support_data['settings']['shadow'] = array(); } @@ -359,7 +386,7 @@ public static function get_block_data() { return static::$blocks; } - $config = array( 'version' => 2 ); + $config = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); foreach ( $blocks as $block_name => $block_type ) { if ( isset( $block_type->supports['__experimentalStyle'] ) ) { $config['styles']['blocks'][ $block_name ] = static::remove_json_comments( $block_type->supports['__experimentalStyle'] ); @@ -494,6 +521,7 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post * Returns the user's origin config. * * @since 5.9.0 + * @since 6.6.0 The 'isGlobalStylesUserThemeJSON' flag is left on the user data. * * @return WP_Theme_JSON Entity that holds styles for user data. */ @@ -531,14 +559,18 @@ public static function get_user_data() { isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && $decoded_data['isGlobalStylesUserThemeJSON'] ) { - unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); $config = $decoded_data; } } /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ - $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); - static::$user = $theme_json->get_theme_json(); + $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); + $config = $theme_json->get_data(); + + // Needs to be set for schema migrations of user data. + $config['isGlobalStylesUserThemeJSON'] = true; + + static::$user = new WP_Theme_JSON( $config, 'custom' ); return static::$user; } @@ -586,7 +618,6 @@ public static function get_merged_data( $origin = 'custom' ) { $result = new WP_Theme_JSON(); $result->merge( static::get_core_data() ); if ( 'default' === $origin ) { - $result->set_spacing_sizes(); return $result; } @@ -597,12 +628,10 @@ public static function get_merged_data( $origin = 'custom' ) { $result->merge( static::get_theme_data() ); if ( 'theme' === $origin ) { - $result->set_spacing_sizes(); return $result; } $result->merge( static::get_user_data() ); - $result->set_spacing_sizes(); return $result; } diff --git a/wp-includes/class-wp-theme-json-schema.php b/wp-includes/class-wp-theme-json-schema.php index 46712a032bc..aa654f25979 100644 --- a/wp-includes/class-wp-theme-json-schema.php +++ b/wp-includes/class-wp-theme-json-schema.php @@ -35,6 +35,7 @@ class WP_Theme_JSON_Schema { * Function that migrates a given theme.json structure to the last version. * * @since 5.9.0 + * @since 6.6.0 Migrate up to v3. * * @param array $theme_json The structure to migrate. * @@ -47,8 +48,14 @@ public static function migrate( $theme_json ) { ); } - if ( 1 === $theme_json['version'] ) { - $theme_json = self::migrate_v1_to_v2( $theme_json ); + // Migrate each version in order starting with the current version. + switch ( $theme_json['version'] ) { + case 1: + $theme_json = self::migrate_v1_to_v2( $theme_json ); + // no break + case 2: + $theme_json = self::migrate_v2_to_v3( $theme_json ); + // no break } return $theme_json; @@ -84,6 +91,88 @@ private static function migrate_v1_to_v2( $old ) { return $new; } + /** + * Migrates from v2 to v3. + * + * - Sets settings.typography.defaultFontSizes to false. + * + * @since 6.6.0 + * + * @param array $old Data to migrate. + * + * @return array Data with defaultFontSizes set to false. + */ + private static function migrate_v2_to_v3( $old ) { + // Copy everything. + $new = $old; + + // Set the new version. + $new['version'] = 3; + + /* + * Remaining changes do not need to be applied to the custom origin, + * as they should take on the value of the theme origin. + */ + if ( + isset( $new['isGlobalStylesUserThemeJSON'] ) && + true === $new['isGlobalStylesUserThemeJSON'] + ) { + return $new; + } + + /* + * Even though defaultFontSizes and defaultSpacingSizes are new + * settings, we need to migrate them as they each control + * PRESETS_METADATA prevent_override values which were previously + * hardcoded to false. This only needs to happen when the theme provides + * fontSizes or spacingSizes as they could match the default ones and + * affect the generated CSS. + */ + if ( isset( $old['settings']['typography']['fontSizes'] ) ) { + if ( ! isset( $new['settings'] ) ) { + $new['settings'] = array(); + } + if ( ! isset( $new['settings']['typography'] ) ) { + $new['settings']['typography'] = array(); + } + $new['settings']['typography']['defaultFontSizes'] = false; + } + + /* + * Similarly to defaultFontSizes, we need to migrate defaultSpacingSizes + * as it controls the PRESETS_METADATA prevent_override which was + * previously hardcoded to false. This only needs to happen when the + * theme provided spacing sizes via spacingSizes or spacingScale. + */ + if ( + isset( $old['settings']['spacing']['spacingSizes'] ) || + isset( $old['settings']['spacing']['spacingScale'] ) + ) { + if ( ! isset( $new['settings'] ) ) { + $new['settings'] = array(); + } + if ( ! isset( $new['settings']['spacing'] ) ) { + $new['settings']['spacing'] = array(); + } + $new['settings']['spacing']['defaultSpacingSizes'] = false; + } + + /* + * In v3 spacingSizes is merged with the generated spacingScale sizes + * instead of completely replacing them. The v3 behavior is what was + * documented for the v2 schema, but the code never actually did work + * that way. Instead of surprising users with a behavior change two + * years after the fact at the same time as a v3 update is introduced, + * we'll continue using the "bugged" behavior for v2 themes. And treat + * the "bug fix" as a breaking change for v3. + */ + if ( isset( $old['settings']['spacing']['spacingSizes'] ) ) { + unset( $new['settings']['spacing']['spacingScale'] ); + } + + return $new; + } + /** * Processes the settings subtree. * diff --git a/wp-includes/class-wp-theme-json.php b/wp-includes/class-wp-theme-json.php index 9aa2b97b8b2..18e0872eb46 100644 --- a/wp-includes/class-wp-theme-json.php +++ b/wp-includes/class-wp-theme-json.php @@ -123,7 +123,9 @@ class WP_Theme_JSON { * `prevent_override` value for `color.duotone` to use `color.defaultDuotone`. * @since 6.2.0 Added 'shadow' presets. * @since 6.3.0 Replaced value_func for duotone with `null`. Custom properties are handled by class-wp-duotone.php. - * @since 6.6.0 Added the `dimensions.aspectRatios` & `dimensions.defaultAspectRatios` preset. + * @since 6.6.0 Added the `dimensions.aspectRatios` and `dimensions.defaultAspectRatios` presets. + * Updated the 'prevent_override' value for font size presets to use 'typography.defaultFontSizes' + * and spacing size presets to use `spacing.defaultSpacingSizes`. * @var array */ const PRESETS_METADATA = array( @@ -169,7 +171,7 @@ class WP_Theme_JSON { ), array( 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => false, + 'prevent_override' => array( 'typography', 'defaultFontSizes' ), 'use_default_names' => true, 'value_func' => 'wp_get_typography_font_size_value', 'css_vars' => '--wp--preset--font-size--$slug', @@ -187,7 +189,7 @@ class WP_Theme_JSON { ), array( 'path' => array( 'spacing', 'spacingSizes' ), - 'prevent_override' => false, + 'prevent_override' => array( 'spacing', 'defaultSpacingSizes' ), 'use_default_names' => true, 'value_key' => 'size', 'css_vars' => '--wp--preset--spacing--$slug', @@ -378,7 +380,8 @@ class WP_Theme_JSON { * `typography.writingMode`, `lightbox.enabled` and `lightbox.allowEditing`. * @since 6.5.0 Added support for `layout.allowCustomContentAndWideSize`, * `background.backgroundSize` and `dimensions.aspectRatio`. - * @since 6.6.0 Added support for `dimensions.aspectRatios` and `dimensions.defaultAspectRatios`. + * @since 6.6.0 Added support for 'dimensions.aspectRatios', 'dimensions.defaultAspectRatios', + * 'typography.defaultFontSizes', and 'spacing.defaultSpacingSizes'. * @var array */ const VALID_SETTINGS = array( @@ -433,33 +436,35 @@ class WP_Theme_JSON { 'sticky' => null, ), 'spacing' => array( - 'customSpacingSize' => null, - 'spacingSizes' => null, - 'spacingScale' => null, - 'blockGap' => null, - 'margin' => null, - 'padding' => null, - 'units' => null, + 'customSpacingSize' => null, + 'defaultSpacingSizes' => null, + 'spacingSizes' => null, + 'spacingScale' => null, + 'blockGap' => null, + 'margin' => null, + 'padding' => null, + 'units' => null, ), 'shadow' => array( 'presets' => null, 'defaultPresets' => null, ), 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textAlign' => null, - 'textColumns' => null, - 'textDecoration' => null, - 'textTransform' => null, - 'writingMode' => null, + 'fluid' => null, + 'customFontSize' => null, + 'defaultFontSizes' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textAlign' => null, + 'textColumns' => null, + 'textDecoration' => null, + 'textTransform' => null, + 'writingMode' => null, ), ); @@ -728,20 +733,23 @@ public static function get_element_class_name( $element ) { * * @since 5.8.0 * @since 5.9.0 Changed value from 1 to 2. + * @since 6.6.0 Changed value from 2 to 3. * @var int */ - const LATEST_SCHEMA = 2; + const LATEST_SCHEMA = 3; /** * Constructor. * * @since 5.8.0 + * @since 6.6.0 Key spacingScale by origin, and Pre-generate the + * spacingSizes from spacingScale. * * @param array $theme_json A structure that follows the theme.json schema. * @param string $origin Optional. What source of data this object represents. * One of 'default', 'theme', or 'custom'. Default 'theme'. */ - public function __construct( $theme_json = array(), $origin = 'theme' ) { + public function __construct( $theme_json = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ), $origin = 'theme' ) { if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) { $origin = 'theme'; } @@ -750,8 +758,8 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); $valid_variations = static::get_valid_block_style_variations(); - $theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); - $this->theme_json = static::maybe_opt_in_into_settings( $theme_json ); + $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); + $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); // Internally, presets are keyed by origin. $nodes = static::get_setting_nodes( $this->theme_json ); @@ -770,6 +778,27 @@ public function __construct( $theme_json = array(), $origin = 'theme' ) { } } } + + // In addition to presets, spacingScale (which generates presets) is also keyed by origin. + $scale_path = array( 'settings', 'spacing', 'spacingScale' ); + $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); + if ( null !== $spacing_scale ) { + // If the spacingScale is not already keyed by origin. + if ( empty( array_intersect( array_keys( $spacing_scale ), static::VALID_ORIGINS ) ) ) { + _wp_array_set( $this->theme_json, $scale_path, array( $origin => $spacing_scale ) ); + } + } + + // Pre-generate the spacingSizes from spacingScale. + $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); + $spacing_scale = _wp_array_get( $this->theme_json, $scale_path, null ); + if ( isset( $spacing_scale ) ) { + $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); + $spacing_sizes = _wp_array_get( $this->theme_json, $sizes_path, array() ); + $spacing_scale_sizes = static::compute_spacing_sizes( $spacing_scale ); + $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); + _wp_array_set( $this->theme_json, $sizes_path, $merged_spacing_sizes ); + } } /** @@ -2915,6 +2944,40 @@ public function merge( $incoming ) { $incoming_data = $incoming->get_raw_data(); $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); + /* + * Recompute all the spacing sizes based on the new hierarchy of data. In the constructor + * spacingScale and spacingSizes are both keyed by origin and VALID_ORIGINS is ordered, so + * we can allow partial spacingScale data to inherit missing data from earlier layers when + * computing the spacing sizes. + * + * This happens before the presets are merged to ensure that default spacing sizes can be + * removed from the theme origin if $prevent_override is true. + */ + $flattened_spacing_scale = array(); + foreach ( static::VALID_ORIGINS as $origin ) { + $scale_path = array( 'settings', 'spacing', 'spacingScale', $origin ); + + // Apply the base spacing scale to the current layer. + $base_spacing_scale = _wp_array_get( $this->theme_json, $scale_path, array() ); + $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $base_spacing_scale ); + + $spacing_scale = _wp_array_get( $incoming_data, $scale_path, null ); + if ( ! isset( $spacing_scale ) ) { + continue; + } + + // Allow partial scale settings by merging with lower layers. + $flattened_spacing_scale = array_replace( $flattened_spacing_scale, $spacing_scale ); + + // Generate and merge the scales for this layer. + $sizes_path = array( 'settings', 'spacing', 'spacingSizes', $origin ); + $spacing_sizes = _wp_array_get( $incoming_data, $sizes_path, array() ); + $spacing_scale_sizes = static::compute_spacing_sizes( $flattened_spacing_scale ); + $merged_spacing_sizes = static::merge_spacing_sizes( $spacing_scale_sizes, $spacing_sizes ); + + _wp_array_set( $incoming_data, $sizes_path, $merged_spacing_sizes ); + } + /* * The array_replace_recursive algorithm merges at the leaf level, * but we don't want leaf arrays to be merged, so we overwrite it. @@ -2951,12 +3014,15 @@ public function merge( $incoming ) { } // Replace the presets. - foreach ( static::PRESETS_METADATA as $preset ) { - $override_preset = ! static::get_metadata_boolean( $this->theme_json['settings'], $preset['prevent_override'], true ); + foreach ( static::PRESETS_METADATA as $preset_metadata ) { + $prevent_override = $preset_metadata['prevent_override']; + if ( is_array( $prevent_override ) ) { + $prevent_override = _wp_array_get( $this->theme_json['settings'], $preset_metadata['prevent_override'] ); + } foreach ( static::VALID_ORIGINS as $origin ) { $base_path = $node['path']; - foreach ( $preset['path'] as $leaf ) { + foreach ( $preset_metadata['path'] as $leaf ) { $base_path[] = $leaf; } @@ -2968,7 +3034,8 @@ public function merge( $incoming ) { continue; } - if ( 'theme' === $origin && $preset['use_default_names'] ) { + // Set names for theme presets based on the slug if they are not set and can use default names. + if ( 'theme' === $origin && $preset_metadata['use_default_names'] ) { foreach ( $content as $key => $item ) { if ( ! isset( $item['name'] ) ) { $name = static::get_name_from_defaults( $item['slug'], $base_path ); @@ -2979,19 +3046,17 @@ public function merge( $incoming ) { } } - if ( - ( 'theme' !== $origin ) || - ( 'theme' === $origin && $override_preset ) - ) { - _wp_array_set( $this->theme_json, $path, $content ); - } else { - $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); - $slugs = array_merge_recursive( $slugs_global, $slugs_node ); + // Filter out default slugs from theme presets when defaults should not be overridden. + if ( 'theme' === $origin && $prevent_override ) { + $slugs_node = static::get_default_slugs( $this->theme_json, $node['path'] ); + $preset_global = _wp_array_get( $slugs_global, $preset_metadata['path'], array() ); + $preset_node = _wp_array_get( $slugs_node, $preset_metadata['path'], array() ); + $preset_slugs = array_merge_recursive( $preset_global, $preset_node ); - $slugs_for_preset = _wp_array_get( $slugs, $preset['path'], array() ); - $content = static::filter_slugs( $content, $slugs_for_preset ); - _wp_array_set( $this->theme_json, $path, $content ); + $content = static::filter_slugs( $content, $preset_slugs ); } + + _wp_array_set( $this->theme_json, $path, $content ); } } } @@ -3530,6 +3595,13 @@ public static function get_from_editor_settings( $settings ) { $theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing']; } + if ( isset( $settings['spacingSizes'] ) ) { + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); + } + $theme_settings['settings']['spacing']['spacingSizes'] = $settings['spacingSizes']; + } + return $theme_settings; } @@ -3703,10 +3775,16 @@ public function get_data() { * Sets the spacingSizes array based on the spacingScale values from theme.json. * * @since 6.1.0 + * @deprecated 6.6.0 + * + * @param string $origin Optional. What source of data to set the spacing sizes for. + * One of 'default', 'theme', or 'custom'. Default 'default'. * * @return null|void */ public function set_spacing_sizes() { + _deprecated_function( __METHOD__, '6.6.0' ); + $spacing_scale = isset( $this->theme_json['settings']['spacing']['spacingScale'] ) ? $this->theme_json['settings']['spacing']['spacingScale'] : array(); @@ -3740,6 +3818,99 @@ public function set_spacing_sizes() { return null; } + $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); + + // If there are 7 or fewer steps in the scale revert to numbers for labels instead of t-shirt sizes. + if ( $spacing_scale['steps'] <= 7 ) { + for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { + $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); + } + } + + _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); + } + + /** + * Merges two sets of spacing size presets. + * + * @since 6.6.0 + * + * @param array $base The base set of spacing sizes. + * @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values. + * @return array The merged set of spacing sizes. + */ + private static function merge_spacing_sizes( $base, $incoming ) { + // Preserve the order if there are no base (spacingScale) values. + if ( empty( $base ) ) { + return $incoming; + } + $merged = array(); + foreach ( $base as $item ) { + $merged[ $item['slug'] ] = $item; + } + foreach ( $incoming as $item ) { + $merged[ $item['slug'] ] = $item; + } + ksort( $merged, SORT_NUMERIC ); + return array_values( $merged ); + } + + /** + * Generates a set of spacing sizes by starting with a medium size and + * applying an operator with an increment value to generate the rest of the + * sizes outward from the medium size. The medium slug is '50' with the rest + * of the slugs being 10 apart. The generated names use t-shirt sizing. + * + * Example: + * + * $spacing_scale = array( + * 'steps' => 4, + * 'mediumStep' => 16, + * 'unit' => 'px', + * 'operator' => '+', + * 'increment' => 2, + * ); + * $spacing_sizes = static::compute_spacing_sizes( $spacing_scale ); + * // -> array( + * // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ), + * // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ), + * // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ), + * // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ), + * // ) + * + * @since 6.6.0 + * + * @param array $spacing_scale { + * The spacing scale values. All are required. + * + * @type int $steps The number of steps in the scale. (up to 10 steps are supported.) + * @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.) + * @type string $unit The CSS unit to use for the sizes. + * @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'. + * @type float $increment The value used with the operator to generate the other sizes. + * } + * @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid. + */ + private static function compute_spacing_sizes( $spacing_scale ) { + /* + * This condition is intentionally missing some checks on ranges for the values in order to + * keep backwards compatibility with the previous implementation. + */ + if ( + ! isset( $spacing_scale['steps'] ) || + ! is_numeric( $spacing_scale['steps'] ) || + 0 === $spacing_scale['steps'] || + ! isset( $spacing_scale['mediumStep'] ) || + ! is_numeric( $spacing_scale['mediumStep'] ) || + ! isset( $spacing_scale['unit'] ) || + ! isset( $spacing_scale['operator'] ) || + ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) || + ! isset( $spacing_scale['increment'] ) || + ! is_numeric( $spacing_scale['increment'] ) + ) { + return array(); + } + $unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] ); $current_step = $spacing_scale['mediumStep']; $steps_mid_point = round( $spacing_scale['steps'] / 2, 0 ); @@ -3822,14 +3993,7 @@ public function set_spacing_sizes() { $spacing_sizes[] = $above_sizes_item; } - // If there are 7 or fewer steps in the scale revert to numbers for labels instead of t-shirt sizes. - if ( $spacing_scale['steps'] <= 7 ) { - for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) { - $spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 ); - } - } - - _wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes ); + return $spacing_sizes; } /** diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php index c7f72d4ec1d..91b5f63e964 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -18,7 +18,7 @@ class WP_REST_Font_Faces_Controller extends WP_REST_Posts_Controller { * @since 6.5.0 * @var int */ - const LATEST_THEME_JSON_VERSION_SUPPORTED = 2; + const LATEST_THEME_JSON_VERSION_SUPPORTED = 3; /** * Whether the controller supports batching. diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php index 184b42d141d..c2d70d0dd69 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php @@ -20,7 +20,7 @@ class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller { * @since 6.5.0 * @var int */ - const LATEST_THEME_JSON_VERSION_SUPPORTED = 2; + const LATEST_THEME_JSON_VERSION_SUPPORTED = 3; /** * Whether the controller supports batching. diff --git a/wp-includes/theme.json b/wp-includes/theme.json index 485d01247d6..c2ea0e71938 100644 --- a/wp-includes/theme.json +++ b/wp-includes/theme.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 2, + "version": 3, "settings": { "appearanceTools": false, "useRootPaddingAwareAlignments": false, @@ -265,6 +265,7 @@ "margin": false, "padding": false, "customSpacingSize": true, + "defaultSpacingSizes": true, "units": [ "px", "em", "rem", "vh", "vw", "%" ], "spacingScale": { "operator": "*", @@ -276,6 +277,7 @@ }, "typography": { "customFontSize": true, + "defaultFontSizes": true, "dropCap": true, "fontSizes": [ { diff --git a/wp-includes/theme.php b/wp-includes/theme.php index 330c36d3f55..fd375437919 100644 --- a/wp-includes/theme.php +++ b/wp-includes/theme.php @@ -2642,6 +2642,7 @@ function get_theme_starter_content() { * @since 6.3.0 The `border` feature allows themes without theme.json to add border styles to blocks. * @since 6.5.0 The `appearance-tools` feature enables a few design tools for blocks, * see `WP_Theme_JSON::APPEARANCE_TOOLS_OPT_INS` for a complete list. + * @since 6.6.0 The `editor-spacing-sizes` feature was added. * * @global array $_wp_theme_features * @@ -2669,6 +2670,7 @@ function get_theme_starter_content() { * - 'editor-color-palette' * - 'editor-gradient-presets' * - 'editor-font-sizes' + * - 'editor-spacing-sizes' * - 'editor-styles' * - 'featured-content' * - 'html5' @@ -4226,6 +4228,31 @@ function create_initial_theme_features() { ), ) ); + register_theme_feature( + 'editor-spacing-sizes', + array( + 'type' => 'array', + 'description' => __( 'Custom spacing sizes if defined by the theme.' ), + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'type' => 'string', + ), + 'size' => array( + 'type' => 'string', + ), + 'slug' => array( + 'type' => 'string', + ), + ), + ), + ), + ), + ) + ); register_theme_feature( 'editor-styles', array( diff --git a/wp-includes/version.php b/wp-includes/version.php index 93df6a8561e..04e532dc37c 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.6-alpha-58327'; +$wp_version = '6.6-alpha-58328'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.