From 1a84edbf55ee1cafe31848f3bf298d7da60caecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Tue, 18 May 2021 17:47:25 +0200 Subject: [PATCH 01/17] Add __experimentalFeatures from theme.json --- src/wp-includes/block-editor.php | 35 ++ .../class-wp-theme-json-resolver.php | 368 +++++++++++++++ src/wp-includes/class-wp-theme-json.php | 375 +++++++++++++++ src/wp-includes/functions.php | 59 +++ src/wp-includes/theme-i18n.json | 106 +++++ src/wp-includes/theme.json | 230 +++++++++ src/wp-settings.php | 2 + .../class-wp-theme-json-resolver-test.php | 202 ++++++++ .../tests/blocks/class-wp-theme-json-test.php | 445 ++++++++++++++++++ .../blocks/data/languages/themes/fse-pl_PL.mo | Bin 0 -> 734 bytes .../blocks/data/languages/themes/fse-pl_PL.po | 31 ++ .../tests/blocks/data/themedir1/fse/style.css | 7 + .../blocks/data/themedir1/fse/theme.json | 45 ++ 13 files changed, 1905 insertions(+) create mode 100644 src/wp-includes/class-wp-theme-json-resolver.php create mode 100644 src/wp-includes/class-wp-theme-json.php create mode 100644 src/wp-includes/theme-i18n.json create mode 100644 src/wp-includes/theme.json create mode 100644 tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php create mode 100644 tests/phpunit/tests/blocks/class-wp-theme-json-test.php create mode 100644 tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.mo create mode 100644 tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.po create mode 100644 tests/phpunit/tests/blocks/data/themedir1/fse/style.css create mode 100644 tests/phpunit/tests/blocks/data/themedir1/fse/theme.json diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 745cdcdb656a8..8d85cfe5d0ae2 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -243,6 +243,41 @@ function get_block_editor_settings( $editor_name, $custom_settings = array() ) { $custom_settings ); + $editor_settings['__experimentalFeatures'] = WP_Theme_JSON_Resolver::get_merged_data( $editor_settings )->get_settings(); + + // These settings may need to be updated based on data coming from theme.json sources. + if ( isset( $editor_settings['__experimentalFeatures']['color']['palette'] ) ) { + $editor_settings['colors'] = $editor_settings['__experimentalFeatures']['color']['palette']; + } + if ( isset( $editor_settings['__experimentalFeatures']['color']['gradients'] ) ) { + $editor_settings['gradients'] = $editor_settings['__experimentalFeatures']['color']['gradients']; + } + if ( isset( $editor_settings['__experimentalFeatures']['color']['custom'] ) ) { + $editor_settings['disableCustomColors'] = $editor_settings['__experimentalFeatures']['color']['custom']; + } + if ( isset( $editor_settings['__experimentalFeatures']['color']['customGradient'] ) ) { + $editor_settings['disableCustomGradients'] = $editor_settings['__experimentalFeatures']['color']['customGradient']; + } + if ( isset( $editor_settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { + $editor_settings['fontSizes'] = $editor_settings['__experimentalFeatures']['typography']['fontSizes']; + } + if ( isset( $editor_settings['__experimentalFeatures']['typography']['customFontSize'] ) ) { + $editor_settings['disableCustomFontSizes'] = $editor_settings['__experimentalFeatures']['typography']['customFontSize']; + } + if ( isset( $editor_settings['__experimentalFeatures']['typography']['customLineHeight'] ) ) { + $editor_settings['enableCustomLineHeight'] = $editor_settings['__experimentalFeatures']['typography']['customLineHeight']; + } + if ( isset( $editor_settings['__experimentalFeatures']['spacing']['units'] ) ) { + if ( ! is_array( $editor_settings['__experimentalFeatures']['spacing']['units'] ) ) { + $editor_settings['enableCustomUnits'] = false; + } else { + $editor_settings['enableCustomUnits'] = count( $editor_settings['__experimentalFeatures']['spacing']['units'] ) > 0; + } + } + if ( isset( $editor_settings['__experimentalFeatures']['spacing']['customPadding'] ) ) { + $editor_settings['enableCustomSpacing'] = $editor_settings['__experimentalFeatures']['spacing']['customPadding']; + } + /** * Filters the settings to pass to the block editor for all editor type. * diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php new file mode 100644 index 0000000000000..5e87bdaae8811 --- /dev/null +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -0,0 +1,368 @@ + [ + * 'path' => [ 'settings', '*', 'typography', 'fontSizes' ], + * 'key' => 'name', + * 'context' => 'Font size name' + * ], + * 1 => [ + * 'path' => [ 'settings', '*', 'typography', 'fontStyles' ], + * 'key' => 'name', + * 'context' => 'Font style name' + * ] + * ] + * + * @param array $i18n_partial A tree that follows the format of i18n-theme.json. + * @param array $current_path Keeps track of the path as we walk down the given tree. + * + * @return array A linear array containing the paths to translate. + */ + private static function extract_paths_to_translate( $i18n_partial, $current_path = array() ) { + $result = array(); + foreach ( $i18n_partial as $property => $partial_child ) { + if ( is_numeric( $property ) ) { + foreach ( $partial_child as $key => $context ) { + return array( + array( + 'path' => $current_path, + 'key' => $key, + 'context' => $context, + ), + ); + } + } + $result = array_merge( + $result, + self::extract_paths_to_translate( $partial_child, array_merge( $current_path, array( $property ) ) ) + ); + } + return $result; + } + + /** + * Returns a data structure used in theme.json translation. + * + * @return array An array of theme.json fields that are translatable and the keys that are translatable + */ + public static function get_fields_to_translate() { + if ( null === self::$theme_json_i18n ) { + $file_structure = self::read_json_file( __DIR__ . '/theme-i18n.json' ); + self::$theme_json_i18n = self::extract_paths_to_translate( $file_structure ); + } + return self::$theme_json_i18n; + } + + /** + * Translates a chunk of the loaded theme.json structure. + * + * @param array $array_to_translate The chunk of theme.json to translate. + * @param string $key The key of the field that contains the string to translate. + * @param string $context The context to apply in the translation call. + * @param string $domain Text domain. Unique identifier for retrieving translated strings. + * + * @return array Returns the modified $theme_json chunk. + */ + private static function translate_theme_json_chunk( array $array_to_translate, $key, $context, $domain ) { + foreach ( $array_to_translate as $item_key => $item_to_translate ) { + if ( empty( $item_to_translate[ $key ] ) ) { + continue; + } + + // phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction,WordPress.WP.I18n.NonSingularStringLiteralText,WordPress.WP.I18n.NonSingularStringLiteralContext,WordPress.WP.I18n.NonSingularStringLiteralDomain + $array_to_translate[ $item_key ][ $key ] = translate_with_gettext_context( $array_to_translate[ $item_key ][ $key ], $context, $domain ); + } + + return $array_to_translate; + } + + /** + * Given a theme.json structure modifies it in place + * to update certain values by its translated strings + * according to the language set by the user. + * + * @param array $theme_json The theme.json to translate. + * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. + * Default 'default'. + * + * @return array Returns the modified $theme_json_structure. + */ + private static function translate( $theme_json, $domain = 'default' ) { + $fields = self::get_fields_to_translate(); + foreach ( $fields as $field ) { + $path = $field['path']; + $key = $field['key']; + $context = $field['context']; + + /* + * We need to process the paths that include '*' separately. + * One example of such a path would be: + * [ 'settings', 'blocks', '*', 'color', 'palette' ] + */ + $nodes_to_iterate = array_keys( $path, '*', true ); + if ( ! empty( $nodes_to_iterate ) ) { + /* + * At the moment, we only need to support one '*' in the path, so take it directly. + * - base will be [ 'settings', 'blocks' ] + * - data will be [ 'color', 'palette' ] + */ + $base_path = array_slice( $path, 0, $nodes_to_iterate[0] ); + $data_path = array_slice( $path, $nodes_to_iterate[0] + 1 ); + $base_tree = _wp_array_get( $theme_json, $base_path, array() ); + foreach ( $base_tree as $node_name => $node_data ) { + $array_to_translate = _wp_array_get( $node_data, $data_path, null ); + if ( is_null( $array_to_translate ) ) { + continue; + } + + // Whole path will be [ 'settings', 'blocks', 'core/paragraph', 'color', 'palette' ]. + $whole_path = array_merge( $base_path, array( $node_name ), $data_path ); + $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); + _wp_array_set( $theme_json, $whole_path, $translated_array ); + } + } else { + $array_to_translate = _wp_array_get( $theme_json, $path, null ); + if ( is_null( $array_to_translate ) ) { + continue; + } + + $translated_array = self::translate_theme_json_chunk( $array_to_translate, $key, $context, $domain ); + _wp_array_set( $theme_json, $path, $translated_array ); + } + } + + return $theme_json; + } + + /** + * Return core's origin config. + * + * @return WP_Theme_JSON Entity that holds core data. + */ + public static function get_core_data() { + if ( null !== self::$core ) { + return self::$core; + } + + $config = self::read_json_file( __DIR__ . '/theme.json' ); + $config = self::translate( $config ); + self::$core = new WP_Theme_JSON( $config ); + + return self::$core; + } + + /** + * Returns the theme's data. + * + * Data from theme.json can be augmented via the + * $theme_support_data variable. This is useful, for example, + * to backfill the gaps in theme.json that a theme has declared + * via add_theme_supports. + * + * Note that if the same data is present in theme.json + * and in $theme_support_data, the theme.json's is not overwritten. + * + * @param array $theme_support_data Theme support data in theme.json format. + * + * @return WP_Theme_JSON Entity that holds theme data. + */ + public static function get_theme_data( $theme_support_data = array() ) { + if ( null === self::$theme ) { + $theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'theme.json' ) ); + // Fallback to experimental-theme.json. + if ( empty( $theme_json_data ) ) { + $theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'experimental-theme.json' ) ); + } + $theme_json_data = self::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); + self::$theme = new WP_Theme_JSON( $theme_json_data ); + } + + if ( empty( $theme_support_data ) ) { + return self::$theme; + } + + /* + * We want the presets and settings declared in theme.json + * to override the ones declared via add_theme_support. + */ + $with_theme_supports = new WP_Theme_JSON( $theme_support_data ); + $with_theme_supports->merge( self::$theme ); + + return $with_theme_supports; + } + + /** + * There are different sources of data for a site: + * core and theme. + * + * While the getters {@link get_core_data}, + * {@link get_theme_data} return the raw data + * from the respective origins, this method merges them + * all together. + * + * If the same piece of data is declared in different origins (core and theme), + * the last origin overrides the previous. For example, + * if core disables custom colors but a theme enables them, + * the theme config wins. + * + * @param array $settings Existing block editor settings. + * Empty array by default. + * + * @return WP_Theme_JSON + */ + public static function get_merged_data( $settings = array() ) { + $theme_support_data = WP_Theme_JSON::get_from_editor_settings( $settings ); + + $result = new WP_Theme_JSON(); + $result->merge( self::get_core_data() ); + $result->merge( self::get_theme_data( $theme_support_data ) ); + + return $result; + } + + /** + * Whether the current theme has a theme.json file. + * + * @return boolean + */ + public static function theme_has_support() { + if ( ! isset( self::$theme_has_support ) ) { + self::$theme_has_support = (bool) self::get_file_path_from_theme( 'theme.json' ); + if ( ! self::$theme_has_support ) { + // Fallback to experimental-theme.json. + self::$theme_has_support = (bool) self::get_file_path_from_theme( 'experimental-theme.json' ); + } + } + + return self::$theme_has_support; + } + + /** + * Builds the path to the given file + * and checks that it is readable. + * + * If it isn't, returns an empty string, + * otherwise returns the whole file path. + * + * @param string $file_name Name of the file. + * @return string The whole file path or empty if the file doesn't exist. + */ + private static function get_file_path_from_theme( $file_name ) { + // This used to be a locate_template call. + // However, that method proved problematic + // due to its use of constants (STYLESHEETPATH) + // that threw errors in some scenarios. + // + // When the theme.json merge algorithm properly supports + // child themes, this should also fallback + // to the template path, as locate_template did. + $located = ''; + $candidate = get_stylesheet_directory() . '/' . $file_name; + if ( is_readable( $candidate ) ) { + $located = $candidate; + } + return $located; + } + + /** + * Cleans the cached data so it can be recalculated. + */ + public static function clean_cached_data() { + self::$core = null; + self::$theme = null; + self::$theme_has_support = null; + self::$theme_json_i18n = null; + } + +} + +add_action( 'switch_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php new file mode 100644 index 0000000000000..4304fc24640d1 --- /dev/null +++ b/src/wp-includes/class-wp-theme-json.php @@ -0,0 +1,375 @@ + array( + 'customColor' => null, + 'customRadius' => null, + 'customStyle' => null, + 'customWidth' => null, + ), + 'color' => array( + 'custom' => null, + 'customGradient' => null, + 'duotone' => null, + 'gradients' => null, + 'link' => null, + 'palette' => null, + ), + 'custom' => null, + 'layout' => null, + 'spacing' => array( + 'customMargin' => null, + 'customPadding' => null, + 'units' => null, + ), + 'typography' => array( + 'customFontSize' => null, + 'customFontStyle' => null, + 'customFontWeight' => null, + 'customLineHeight' => null, + 'customTextDecorations' => null, + 'customTextTransforms' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + ), + ); + + const LATEST_SCHEMA = 1; + + /** + * Constructor. + * + * @param array $theme_json A structure that follows the theme.json schema. + */ + public function __construct( $theme_json = array() ) { + if ( ! isset( $theme_json['version'] ) || self::LATEST_SCHEMA !== $theme_json['version'] ) { + $this->theme_json = array(); + return; + } + + $valid_block_names = array_keys( WP_Block_Type_Registry::get_instance()->get_all_registered() ); + $this->theme_json = self::sanitize( $theme_json, $valid_block_names ); + } + + /** + * Sanitizes the input according to the schemas. + * + * @param array $input Structure to sanitize. + * @param array $valid_block_names List of valid block names. + * + * @return array The sanitized output. + */ + private static function sanitize( $input, $valid_block_names ) { + $output = array(); + + if ( ! is_array( $input ) ) { + return $output; + } + + $output = array_intersect_key( $input, array_flip( self::VALID_TOP_LEVEL_KEYS ) ); + + // Build the schema. + $schema = array(); + $schema_settings_blocks = array(); + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = self::VALID_SETTINGS; + } + $schema['settings'] = self::VALID_SETTINGS; + $schema['settings']['blocks'] = $schema_settings_blocks; + + // Remove anything that's not present in the schema. + foreach ( array( 'settings' ) as $subtree ) { + if ( ! isset( $input[ $subtree ] ) ) { + continue; + } + + if ( ! is_array( $input[ $subtree ] ) ) { + unset( $output[ $subtree ] ); + continue; + } + + $result = self::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); + + if ( empty( $result ) ) { + unset( $output[ $subtree ] ); + } else { + $output[ $subtree ] = $result; + } + } + + return $output; + } + + /** + * Given a tree, removes the keys that are not present in the schema. + * + * It is recursive and modifies the input in-place. + * + * @param array $tree Input to process. + * @param array $schema Schema to adhere to. + * + * @return array Returns the modified $tree. + */ + private static function remove_keys_not_in_schema( $tree, $schema ) { + $tree = array_intersect_key( $tree, $schema ); + + foreach ( $schema as $key => $data ) { + if ( ! isset( $tree[ $key ] ) ) { + continue; + } + + if ( is_array( $schema[ $key ] ) && is_array( $tree[ $key ] ) ) { + $tree[ $key ] = self::remove_keys_not_in_schema( $tree[ $key ], $schema[ $key ] ); + + if ( empty( $tree[ $key ] ) ) { + unset( $tree[ $key ] ); + } + } elseif ( is_array( $schema[ $key ] ) && ! is_array( $tree[ $key ] ) ) { + unset( $tree[ $key ] ); + } + } + + return $tree; + } + + /** + * Returns the existing settings for each block. + * + * Example: + * + * { + * 'root': { + * 'color': { + * 'custom': true + * } + * }, + * 'core/paragraph': { + * 'spacing': { + * 'customPadding': true + * } + * } + * } + * + * @return array Settings per block. + */ + public function get_settings() { + if ( ! isset( $this->theme_json['settings'] ) ) { + return array(); + } else { + return $this->theme_json['settings']; + } + } + + /** + * Builds metadata for the setting nodes, which returns in the form of: + * + * [ + * [ + * 'path' => ['path', 'to', 'some', 'node' ] + * ], + * [ + * 'path' => [ 'path', 'to', 'other', 'node' ] + * ], + * ] + * + * @param array $theme_json The tree to extract setting nodes from. + * + * @return array + */ + private static function get_setting_nodes( $theme_json ) { + $nodes = array(); + if ( ! isset( $theme_json['settings'] ) ) { + return $nodes; + } + + // Top-level. + $nodes[] = array( + 'path' => array( 'settings' ), + ); + + // Calculate paths for blocks. + if ( ! isset( $theme_json['settings']['blocks'] ) ) { + return $nodes; + } + + foreach ( $theme_json['settings']['blocks'] as $name => $node ) { + $nodes[] = array( + 'path' => array( 'settings', 'blocks', $name ), + ); + } + + return $nodes; + } + + /** + * Merge new incoming data. + * + * @param WP_Theme_JSON $incoming Data to merge. + */ + public function merge( $incoming ) { + $incoming_data = $incoming->get_raw_data(); + $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); + + // The array_replace_recursive algorithm merges at the leaf level. + // For leaf values that are arrays it will use the numeric indexes for replacement. + // In those cases, what we want is to use the incoming value, if it exists. + // + // These are the cases that have array values at the leaf levels. + $properties = array(); + $properties[] = array( 'color', 'palette' ); + $properties[] = array( 'color', 'gradients' ); + $properties[] = array( 'custom' ); + $properties[] = array( 'spacing', 'units' ); + $properties[] = array( 'typography', 'fontSizes' ); + $properties[] = array( 'typography', 'fontFamilies' ); + + $nodes = self::get_setting_nodes( $this->theme_json ); + foreach ( $nodes as $metadata ) { + foreach ( $properties as $property_path ) { + $path = array_merge( $metadata['path'], $property_path ); + $node = _wp_array_get( $incoming_data, $path, array() ); + if ( ! empty( $node ) ) { + _wp_array_set( $this->theme_json, $path, $node ); + } + } + } + + } + + /** + * Returns the raw data. + * + * @return array Raw data. + */ + public function get_raw_data() { + return $this->theme_json; + } + + /** + * + * Transforms the given editor settings according the + * add_theme_support format to the theme.json format. + * + * @param array $settings Existing editor settings. + * + * @return array Config that adheres to the theme.json schema. + */ + public static function get_from_editor_settings( $settings ) { + $theme_settings = array( + 'version' => self::LATEST_SCHEMA, + 'settings' => array(), + ); + + // Deprecated theme supports. + if ( isset( $settings['disableCustomColors'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors']; + } + + if ( isset( $settings['disableCustomGradients'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients']; + } + + if ( isset( $settings['disableCustomFontSizes'] ) ) { + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); + } + $theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes']; + } + + if ( isset( $settings['enableCustomLineHeight'] ) ) { + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); + } + $theme_settings['settings']['typography']['customLineHeight'] = $settings['enableCustomLineHeight']; + } + + if ( isset( $settings['enableCustomUnits'] ) ) { + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); + } + $theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ? + array( 'px', 'em', 'rem', 'vh', 'vw' ) : + $settings['enableCustomUnits']; + } + + if ( isset( $settings['colors'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['palette'] = $settings['colors']; + } + + if ( isset( $settings['gradients'] ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['gradients'] = $settings['gradients']; + } + + if ( isset( $settings['fontSizes'] ) ) { + $font_sizes = $settings['fontSizes']; + // Back-compatibility for presets without units. + foreach ( $font_sizes as $key => $font_size ) { + if ( is_numeric( $font_size['size'] ) ) { + $font_sizes[ $key ]['size'] = $font_size['size'] . 'px'; + } + } + if ( ! isset( $theme_settings['settings']['typography'] ) ) { + $theme_settings['settings']['typography'] = array(); + } + $theme_settings['settings']['typography']['fontSizes'] = $font_sizes; + } + + // This allows to make the plugin work with WordPress 5.7 beta + // as well as lower versions. The second check can be removed + // as soon as the minimum WordPress version for the plugin + // is bumped to 5.7. + if ( isset( $settings['enableCustomSpacing'] ) ) { + if ( ! isset( $theme_settings['settings']['spacing'] ) ) { + $theme_settings['settings']['spacing'] = array(); + } + $theme_settings['settings']['spacing']['customPadding'] = $settings['enableCustomSpacing']; + } + + // Things that didn't land in core yet, so didn't have a setting assigned. + if ( current( (array) get_theme_support( 'experimental-link-color' ) ) ) { + if ( ! isset( $theme_settings['settings']['color'] ) ) { + $theme_settings['settings']['color'] = array(); + } + $theme_settings['settings']['color']['link'] = true; + } + + return $theme_settings; + } + +} diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 3c7e204db33e4..0c6f44591f6ed 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -4626,6 +4626,65 @@ function _wp_array_get( $array, $path, $default = null ) { return $array; } +/** + * Sets an array in depth based on a path of keys. + * + * It is the PHP equivalent of JavaScript's `lodash.set()` and mirroring it may help other components + * retain some symmetry between client and server implementations. + * + * Example usage: + * + * $array = array(); + * _wp_array_set( $array, array( 'a', 'b', 'c', 1 ); + * $array becomes: + * array( + * 'a' => array( + * 'b' => array( + * 'c' => 1, + * ), + * ), + * ); + * + * @param array $array An array that we want to mutate to include a specific value in a path. + * @param array $path An array of keys describing the path that we want to mutate. + * @param mixed $value The value that will be set. + */ +function _wp_array_set( &$array, $path, $value = null ) { + // Confirm $array is valid. + if ( ! is_array( $array ) ) { + return; + } + + // Confirm $path is valid. + if ( ! is_array( $path ) ) { + return; + } + $path_length = count( $path ); + if ( 0 === $path_length ) { + return; + } + foreach ( $path as $path_element ) { + if ( + ! is_string( $path_element ) && ! is_integer( $path_element ) && + ! is_null( $path_element ) + ) { + return; + } + } + + for ( $i = 0; $i < $path_length - 1; ++$i ) { + $path_element = $path[ $i ]; + if ( + ! array_key_exists( $path_element, $array ) || + ! is_array( $array[ $path_element ] ) + ) { + $array[ $path_element ] = array(); + } + $array = &$array[ $path_element ]; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration + } + $array[ $path[ $i ] ] = $value; +} + /** * Determines if the variable is a numeric-indexed array. * diff --git a/src/wp-includes/theme-i18n.json b/src/wp-includes/theme-i18n.json new file mode 100644 index 0000000000000..24c55c17cd665 --- /dev/null +++ b/src/wp-includes/theme-i18n.json @@ -0,0 +1,106 @@ +{ + "settings": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ], + "fontStyles": [ + { + "name": "Font style name" + } + ], + "fontWeights": [ + { + "name": "Font weight name" + } + ], + "fontFamilies": [ + { + "name": "Font family name" + } + ], + "textTransforms": [ + { + "name": "Text transform name" + } + ], + "textDecorations": [ + { + "name": "Text decoration name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ], + "duotone": [ + { + "name": "Duotone name" + } + ] + }, + "blocks": { + "*": { + "typography": { + "fontSizes": [ + { + "name": "Font size name" + } + ], + "fontStyles": [ + { + "name": "Font style name" + } + ], + "fontWeights": [ + { + "name": "Font weight name" + } + ], + "fontFamilies": [ + { + "name": "Font family name" + } + ], + "textTransforms": [ + { + "name": "Text transform name" + } + ], + "textDecorations": [ + { + "name": "Text decoration name" + } + ] + }, + "color": { + "palette": [ + { + "name": "Color name" + } + ], + "gradients": [ + { + "name": "Gradient name" + } + ] + } + } + } + }, + "customTemplates": [ + { + "title": "Custom template name" + } + ] +} diff --git a/src/wp-includes/theme.json b/src/wp-includes/theme.json new file mode 100644 index 0000000000000..d92547a146895 --- /dev/null +++ b/src/wp-includes/theme.json @@ -0,0 +1,230 @@ +{ + "version": 1, + "settings": { + "color": { + "palette": [ + { + "name": "Black", + "slug": "black", + "color": "#000000" + }, + { + "name": "Cyan bluish gray", + "slug": "cyan-bluish-gray", + "color": "#abb8c3" + }, + { + "name": "White", + "slug": "white", + "color": "#ffffff" + }, + { + "name": "Pale pink", + "slug": "pale-pink", + "color": "#f78da7" + }, + { + "name": "Vivid red", + "slug": "vivid-red", + "color": "#cf2e2e" + }, + { + "name": "Luminous vivid orange", + "slug": "luminous-vivid-orange", + "color": "#ff6900" + }, + { + "name": "Luminous vivid amber", + "slug": "luminous-vivid-amber", + "color": "#fcb900" + }, + { + "name": "Light green cyan", + "slug": "light-green-cyan", + "color": "#7bdcb5" + }, + { + "name": "Vivid green cyan", + "slug": "vivid-green-cyan", + "color": "#00d084" + }, + { + "name": "Pale cyan blue", + "slug": "pale-cyan-blue", + "color": "#8ed1fc" + }, + { + "name": "Vivid cyan blue", + "slug": "vivid-cyan-blue", + "color": "#0693e3" + }, + { + "name": "Vivid purple", + "slug": "vivid-purple", + "color": "#9b51e0" + } + ], + "gradients": [ + { + "name": "Vivid cyan blue to vivid purple", + "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)", + "slug": "vivid-cyan-blue-to-vivid-purple" + }, + { + "name": "Light green cyan to vivid green cyan", + "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)", + "slug": "light-green-cyan-to-vivid-green-cyan" + }, + { + "name": "Luminous vivid amber to luminous vivid orange", + "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)", + "slug": "luminous-vivid-amber-to-luminous-vivid-orange" + }, + { + "name": "Luminous vivid orange to vivid red", + "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)", + "slug": "luminous-vivid-orange-to-vivid-red" + }, + { + "name": "Very light gray to cyan bluish gray", + "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)", + "slug": "very-light-gray-to-cyan-bluish-gray" + }, + { + "name": "Cool to warm spectrum", + "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)", + "slug": "cool-to-warm-spectrum" + }, + { + "name": "Blush light purple", + "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)", + "slug": "blush-light-purple" + }, + { + "name": "Blush bordeaux", + "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)", + "slug": "blush-bordeaux" + }, + { + "name": "Luminous dusk", + "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)", + "slug": "luminous-dusk" + }, + { + "name": "Pale ocean", + "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)", + "slug": "pale-ocean" + }, + { + "name": "Electric grass", + "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)", + "slug": "electric-grass" + }, + { + "name": "Midnight", + "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)", + "slug": "midnight" + } + ], + "duotone": [ + { + "name": "Dark grayscale" , + "colors": [ "#000000", "#7f7f7f" ], + "slug": "dark-grayscale" + }, + { + "name": "Grayscale" , + "colors": [ "#000000", "#ffffff" ], + "slug": "grayscale" + }, + { + "name": "Purple and yellow" , + "colors": [ "#8c00b7", "#fcff41" ], + "slug": "purple-yellow" + }, + { + "name": "Blue and red" , + "colors": [ "#000097", "#ff4747" ], + "slug": "blue-red" + }, + { + "name": "Midnight" , + "colors": [ "#000000", "#00a5ff" ], + "slug": "midnight" + }, + { + "name": "Magenta and yellow" , + "colors": [ "#c7005a", "#fff278" ], + "slug": "magenta-yellow" + }, + { + "name": "Purple and green" , + "colors": [ "#a60072", "#67ff66" ], + "slug": "purple-green" + }, + { + "name": "Blue and orange" , + "colors": [ "#1900d8", "#ffa96b" ], + "slug": "blue-orange" + } + ], + "custom": true, + "link": false, + "customGradient": true + }, + "typography": { + "dropCap": true, + "customFontSize": true, + "customLineHeight": false, + "customFontStyle": true, + "customFontWeight": true, + "customTextTransforms": true, + "customTextDecorations": true, + "fontSizes": [ + { + "name": "Small", + "slug": "small", + "size": "13px" + }, + { + "name": "Normal", + "slug": "normal", + "size": "16px" + }, + { + "name": "Medium", + "slug": "medium", + "size": "20px" + }, + { + "name": "Large", + "slug": "large", + "size": "36px" + }, + { + "name": "Huge", + "slug": "huge", + "size": "42px" + } + ] + }, + "spacing": { + "customMargin": false, + "customPadding": false, + "units": [ "px", "em", "rem", "vh", "vw" ] + }, + "border": { + "customColor": false, + "customRadius": false, + "customStyle": false, + "customWidth": false + }, + "blocks": { + "core/button": { + "border": { + "customRadius": true + } + } + } + } +} diff --git a/src/wp-settings.php b/src/wp-settings.php index d937cb4fa23c5..b668ba8b56aac 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -294,6 +294,8 @@ require ABSPATH . WPINC . '/class-wp-block-parser.php'; require ABSPATH . WPINC . '/blocks.php'; require ABSPATH . WPINC . '/blocks/index.php'; +require ABSPATH . WPINC . '/class-wp-theme-json.php'; +require ABSPATH . WPINC . '/class-wp-theme-json-resolver.php'; require ABSPATH . WPINC . '/block-editor.php'; require ABSPATH . WPINC . '/block-patterns.php'; require ABSPATH . WPINC . '/class-wp-block-supports.php'; diff --git a/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php b/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php new file mode 100644 index 0000000000000..4984fd1fdfd2f --- /dev/null +++ b/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php @@ -0,0 +1,202 @@ +theme_root = realpath( __DIR__ . '/data/themedir1' ); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + function tearDown() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + parent::tearDown(); + } + + function filter_set_theme_root() { + return $this->theme_root; + } + + function filter_set_locale_to_polish() { + return 'pl_PL'; + } + + function test_fields_are_extracted() { + $actual = WP_Theme_JSON_Resolver::get_fields_to_translate(); + + $expected = array( + array( + 'path' => array( 'settings', 'typography', 'fontSizes' ), + 'key' => 'name', + 'context' => 'Font size name', + ), + array( + 'path' => array( 'settings', 'typography', 'fontStyles' ), + 'key' => 'name', + 'context' => 'Font style name', + ), + array( + 'path' => array( 'settings', 'typography', 'fontWeights' ), + 'key' => 'name', + 'context' => 'Font weight name', + ), + array( + 'path' => array( 'settings', 'typography', 'fontFamilies' ), + 'key' => 'name', + 'context' => 'Font family name', + ), + array( + 'path' => array( 'settings', 'typography', 'textTransforms' ), + 'key' => 'name', + 'context' => 'Text transform name', + ), + array( + 'path' => array( 'settings', 'typography', 'textDecorations' ), + 'key' => 'name', + 'context' => 'Text decoration name', + ), + array( + 'path' => array( 'settings', 'color', 'palette' ), + 'key' => 'name', + 'context' => 'Color name', + ), + array( + 'path' => array( 'settings', 'color', 'gradients' ), + 'key' => 'name', + 'context' => 'Gradient name', + ), + array( + 'path' => array( 'settings', 'color', 'duotone' ), + 'key' => 'name', + 'context' => 'Duotone name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontSizes' ), + 'key' => 'name', + 'context' => 'Font size name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontStyles' ), + 'key' => 'name', + 'context' => 'Font style name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontWeights' ), + 'key' => 'name', + 'context' => 'Font weight name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontFamilies' ), + 'key' => 'name', + 'context' => 'Font family name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'textTransforms' ), + 'key' => 'name', + 'context' => 'Text transform name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'typography', 'textDecorations' ), + 'key' => 'name', + 'context' => 'Text decoration name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'color', 'palette' ), + 'key' => 'name', + 'context' => 'Color name', + ), + array( + 'path' => array( 'settings', 'blocks', '*', 'color', 'gradients' ), + 'key' => 'name', + 'context' => 'Gradient name', + ), + array( + 'path' => array( 'customTemplates' ), + 'key' => 'title', + 'context' => 'Custom template name', + ), + ); + + $this->assertEquals( $expected, $actual ); + } + + function test_translations_are_applied() { + add_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); + load_textdomain( 'fse', realpath( __DIR__ . '/data/languages/themes/fse-pl_PL.mo' ) ); + + switch_theme( 'fse' ); + + $actual = WP_Theme_JSON_Resolver::get_theme_data(); + + unload_textdomain( 'fse' ); + remove_filter( 'locale', array( $this, 'filter_set_locale_to_polish' ) ); + + $this->assertSame( wp_get_theme()->get( 'TextDomain' ), 'fse' ); + $this->assertSame( + array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'light', + 'name' => 'Jasny', + 'color' => '#f5f7f9', + ), + array( + 'slug' => 'dark', + 'name' => 'Ciemny', + 'color' => '#000', + ), + ), + 'custom' => false, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'light', + 'name' => 'Jasny', + 'color' => '#f5f7f9', + ), + ), + ), + ), + ), + ), + $actual->get_settings() + ); + } + + function test_switching_themes_recalculates_data() { + // By default, the theme for unit tests is "default", + // which doesn't have theme.json support. + $default = WP_Theme_JSON_Resolver::theme_has_support(); + + // Switch to a theme that does have support. + switch_theme( 'fse' ); + $fse = WP_Theme_JSON_Resolver::theme_has_support(); + + $this->assertSame( false, $default ); + $this->assertSame( true, $fse ); + } + +} diff --git a/tests/phpunit/tests/blocks/class-wp-theme-json-test.php b/tests/phpunit/tests/blocks/class-wp-theme-json-test.php new file mode 100644 index 0000000000000..3ecb6061e6cd1 --- /dev/null +++ b/tests/phpunit/tests/blocks/class-wp-theme-json-test.php @@ -0,0 +1,445 @@ + WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + ), + 'invalid/key' => 'value', + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => false, + ), + 'invalid/key' => 'value', + ), + ), + ), + 'styles' => array( + 'color' => array( + 'link' => 'blue', + ), + ), + ) + ); + + $actual = $theme_json->get_settings(); + + $expected = array( + 'color' => array( + 'custom' => false, + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'custom' => false, + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_merge_incoming_data() { + $initial = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + array( + 'slug' => 'green', + 'color' => 'green', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'custom' => false, + ), + ), + ), + ), + 'styles' => array( + 'typography' => array( + 'fontSize' => '12', + ), + ), + ); + + $add_new_block = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'blocks' => array( + 'core/list' => array( + 'color' => array( + 'custom' => false, + ), + ), + ), + ), + 'styles' => array( + 'blocks' => array( + 'core/list' => array( + 'typography' => array( + 'fontSize' => '12', + ), + 'color' => array( + 'background' => 'brown', + ), + ), + ), + ), + ); + + $add_key_in_settings = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'customGradient' => true, + ), + ), + ); + + $update_key_in_settings = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, + ), + ), + ); + + $add_styles = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'top' => '12px', + ), + ), + ), + ), + ), + ); + + $add_key_in_styles = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'padding' => array( + 'bottom' => '12px', + ), + ), + ), + ), + ), + ); + + $add_invalid_context = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/para' => array( + 'typography' => array( + 'lineHeight' => '12', + ), + ), + ), + ), + ); + + $update_presets = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', + ), + ), + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', + ), + ), + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, + 'customGradient' => true, + 'palette' => array( + array( + 'slug' => 'blue', + 'color' => 'blue', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'fontSize', + 'size' => 'fontSize', + ), + ), + 'fontFamilies' => array( + array( + 'slug' => 'fontFamily', + 'fontFamily' => 'fontFamily', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'custom' => false, + ), + ), + 'core/list' => array( + 'color' => array( + 'custom' => false, + ), + ), + ), + ), + ); + + $theme_json = new WP_Theme_JSON( $initial ); + $theme_json->merge( new WP_Theme_JSON( $add_new_block ) ); + $theme_json->merge( new WP_Theme_JSON( $add_key_in_settings ) ); + $theme_json->merge( new WP_Theme_JSON( $update_key_in_settings ) ); + $theme_json->merge( new WP_Theme_JSON( $add_styles ) ); + $theme_json->merge( new WP_Theme_JSON( $add_key_in_styles ) ); + $theme_json->merge( new WP_Theme_JSON( $add_invalid_context ) ); + $theme_json->merge( new WP_Theme_JSON( $update_presets ) ); + $actual = $theme_json->get_raw_data(); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_get_from_editor_settings() { + $input = array( + 'disableCustomColors' => true, + 'disableCustomGradients' => true, + 'disableCustomFontSizes' => true, + 'enableCustomLineHeight' => true, + 'enableCustomUnits' => true, + 'colors' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', + ), + ), + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', + ), + ), + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + 'customGradient' => false, + 'gradients' => array( + array( + 'slug' => 'gradient-slug', + 'name' => 'Gradient Name', + 'gradient' => 'gradientvalue', + ), + ), + 'palette' => array( + array( + 'slug' => 'color-slug', + 'name' => 'Color Name', + 'color' => 'colorvalue', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), + ), + 'typography' => array( + 'customFontSize' => false, + 'customLineHeight' => true, + 'fontSizes' => array( + array( + 'slug' => 'size-slug', + 'name' => 'Size Name', + 'size' => 'sizevalue', + ), + ), + ), + ), + ); + + $actual = WP_Theme_JSON::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_get_editor_settings_no_theme_support() { + $input = array( + '__unstableEnableFullSiteEditingBlocks' => false, + 'disableCustomColors' => false, + 'disableCustomFontSizes' => false, + 'disableCustomGradients' => false, + 'enableCustomLineHeight' => false, + 'enableCustomUnits' => false, + 'imageSizes' => array( + array( + 'slug' => 'thumbnail', + 'name' => 'Thumbnail', + ), + array( + 'slug' => 'medium', + 'name' => 'Medium', + ), + array( + 'slug' => 'large', + 'name' => 'Large', + ), + array( + 'slug' => 'full', + 'name' => 'Full Size', + ), + ), + 'isRTL' => false, + 'maxUploadFileSize' => 123, + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => true, + 'customGradient' => true, + ), + 'spacing' => array( + 'units' => false, + ), + 'typography' => array( + 'customFontSize' => true, + 'customLineHeight' => false, + ), + ), + ); + + $actual = WP_Theme_JSON::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_get_editor_settings_blank() { + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'settings' => array(), + ); + $actual = WP_Theme_JSON::get_from_editor_settings( array() ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + function test_get_editor_settings_custom_units_can_be_disabled() { + add_theme_support( 'custom-units', array() ); + $input = get_default_block_editor_settings(); + + $expected = array( + 'units' => array( array() ), + 'customPadding' => false, + ); + + $actual = WP_Theme_JSON::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); + } + + function test_get_editor_settings_custom_units_can_be_enabled() { + add_theme_support( 'custom-units' ); + $input = get_default_block_editor_settings(); + + $expected = array( + 'units' => array( 'px', 'em', 'rem', 'vh', 'vw' ), + 'customPadding' => false, + ); + + $actual = WP_Theme_JSON::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); + } + + function test_get_editor_settings_custom_units_can_be_filtered() { + add_theme_support( 'custom-units', 'rem', 'em' ); + $input = get_default_block_editor_settings(); + + $expected = array( + 'units' => array( 'rem', 'em' ), + 'customPadding' => false, + ); + + $actual = WP_Theme_JSON::get_from_editor_settings( $input ); + + $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); + } + +} diff --git a/tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.mo b/tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.mo new file mode 100644 index 0000000000000000000000000000000000000000..0ff620e64ae4adbb89fd2f1ea31f2ea65bab72e1 GIT binary patch literal 734 zcmYjO!EVz)5M3Z}@CC$)1Bc~;KxBjM6sfYwg|vl2Dl8HZ2PA9j46$2#*X(X+!wo(G zi7(*-Cl08;!KWaePl@D6CK2Cf4ifE&PP;4bhL z_rxx8WJSFlX^#KrlVXuOHeOn%EAFVO zOX;XR`b<|;%LR35mKc3RnG^eS@rH~ow2HWwycDnKW6?0NgXV~bei#UUD1t{k7)F8r zzz=-in@F`-VqC=QGEFAl!T!PSW=t^fy|Gq~loPX6jZ=lmRfP^ Rj8-eYIQ#PZ`-!3>_79nA*+&2X literal 0 HcmV?d00001 diff --git a/tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.po b/tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.po new file mode 100644 index 0000000000000..d55e02b8b5468 --- /dev/null +++ b/tests/phpunit/tests/blocks/data/languages/themes/fse-pl_PL.po @@ -0,0 +1,31 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: 2015-12-31 16:31+0100\n" +"PO-Revision-Date: 2021-03-15 13:10+0100\n" +"Language: pl_PL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.2\n" +"X-Poedit-Basepath: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Poedit-KeywordsList: __;_e;_x:1,2c;_ex:1,2c;_n:1,2;_nx:1,2,4c;_n_noop:1,2;" +"_nx_noop:1,2,3c;esc_attr__;esc_html__;esc_attr_e;esc_html_e;esc_attr_x:1,2c;" +"esc_html_x:1,2c\n" +"X-Textdomain-Support: yes\n" +"Last-Translator: \n" +"Language-Team: \n" +"X-Poedit-SearchPath-0: .\n" + +msgctxt "Custom template name" +msgid "Homepage template" +msgstr "Szablon strony głównej" + +msgctxt "Color name" +msgid "Light" +msgstr "Jasny" + +msgctxt "Color name" +msgid "Dark" +msgstr "Ciemny" diff --git a/tests/phpunit/tests/blocks/data/themedir1/fse/style.css b/tests/phpunit/tests/blocks/data/themedir1/fse/style.css new file mode 100644 index 0000000000000..efc417305327a --- /dev/null +++ b/tests/phpunit/tests/blocks/data/themedir1/fse/style.css @@ -0,0 +1,7 @@ +/* +Theme Name: FSE Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Version: 1.0.0 +Text Domain: fse +*/ diff --git a/tests/phpunit/tests/blocks/data/themedir1/fse/theme.json b/tests/phpunit/tests/blocks/data/themedir1/fse/theme.json new file mode 100644 index 0000000000000..bda2c7646305c --- /dev/null +++ b/tests/phpunit/tests/blocks/data/themedir1/fse/theme.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "settings": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f5f7f9" + }, + { + "slug": "dark", + "name": "Dark", + "color": "#000" + } + ], + "custom": false + }, + "blocks": { + "core/paragraph": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f5f7f9" + } + ] + } + } + } + }, + "customTemplates": [ + { + "name": "page-home", + "title": "Homepage template" + } + ], + "templateParts": [ + { + "name": "small-header", + "area": "header" + } + ] +} From 0113f8823fb9783ac177532310a839bd1f0a007e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 09:53:17 +0200 Subject: [PATCH 02/17] Fix linting issue --- src/wp-includes/class-wp-theme-json-resolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 5e87bdaae8811..57c03a210b15d 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -288,7 +288,7 @@ public static function get_theme_data( $theme_support_data = array() ) { * {@link get_theme_data} return the raw data * from the respective origins, this method merges them * all together. - * + * * If the same piece of data is declared in different origins (core and theme), * the last origin overrides the previous. For example, * if core disables custom colors but a theme enables them, From 8989a4e4f196a0684af5f2d828607868d4728939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 10:15:12 +0200 Subject: [PATCH 03/17] Allow filtering settings and remove non stable ones --- src/wp-includes/class-wp-theme-json.php | 27 ++++++++++++------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 4304fc24640d1..d8e8f8926e536 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -23,13 +23,7 @@ class WP_Theme_JSON { 'settings', ); - const VALID_SETTINGS = array( - 'border' => array( - 'customColor' => null, - 'customRadius' => null, - 'customStyle' => null, - 'customWidth' => null, - ), + const ALLOWED_SETTINGS = array( 'color' => array( 'custom' => null, 'customGradient' => null, @@ -47,13 +41,8 @@ class WP_Theme_JSON { ), 'typography' => array( 'customFontSize' => null, - 'customFontStyle' => null, - 'customFontWeight' => null, 'customLineHeight' => null, - 'customTextDecorations' => null, - 'customTextTransforms' => null, 'dropCap' => null, - 'fontFamilies' => null, 'fontSizes' => null, ), ); @@ -75,6 +64,15 @@ public function __construct( $theme_json = array() ) { $this->theme_json = self::sanitize( $theme_json, $valid_block_names ); } + /** + * Returns the allowed settings in a theme.json structure. + * + * @return array + */ + private static function get_allowed_settings() { + return apply_filters( 'theme_json_allowed_settings', self::ALLOWED_SETTINGS ); + } + /** * Sanitizes the input according to the schemas. * @@ -95,10 +93,11 @@ private static function sanitize( $input, $valid_block_names ) { // Build the schema. $schema = array(); $schema_settings_blocks = array(); + $allowed_settings = self::get_allowed_settings(); foreach ( $valid_block_names as $block ) { - $schema_settings_blocks[ $block ] = self::VALID_SETTINGS; + $schema_settings_blocks[ $block ] = $allowed_settings; } - $schema['settings'] = self::VALID_SETTINGS; + $schema['settings'] = $allowed_settings; $schema['settings']['blocks'] = $schema_settings_blocks; // Remove anything that's not present in the schema. From b6035e0a7ed70a7537cef8c25a6e28032d96cc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 10:40:20 +0200 Subject: [PATCH 04/17] Follow naming schema --- src/wp-includes/class-wp-theme-json.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index d8e8f8926e536..10e32f3406e1b 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -18,7 +18,7 @@ class WP_Theme_JSON { */ private $theme_json = null; - const VALID_TOP_LEVEL_KEYS = array( + const ALLOWED_TOP_LEVEL_KEYS = array( 'version', 'settings', ); @@ -88,7 +88,7 @@ private static function sanitize( $input, $valid_block_names ) { return $output; } - $output = array_intersect_key( $input, array_flip( self::VALID_TOP_LEVEL_KEYS ) ); + $output = array_intersect_key( $input, array_flip( self::ALLOWED_TOP_LEVEL_KEYS ) ); // Build the schema. $schema = array(); From 4691d19a0ee037ae3573142d486a57b3bdadb4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 11:00:26 +0200 Subject: [PATCH 05/17] Refactor theme_json_allowed_settings to theme_json_allowed_schema --- src/wp-includes/class-wp-theme-json.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 10e32f3406e1b..c599940fca8f3 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -65,12 +65,18 @@ public function __construct( $theme_json = array() ) { } /** - * Returns the allowed settings in a theme.json structure. + * Returns the allowed schema for a theme.json structure. * * @return array */ - private static function get_allowed_settings() { - return apply_filters( 'theme_json_allowed_settings', self::ALLOWED_SETTINGS ); + private static function get_allowed_schema() { + return apply_filters( + 'theme_json_allowed_schema', + array( + 'topLevel' => self::ALLOWED_TOP_LEVEL_KEYS, + 'settings' => self::ALLOWED_SETTINGS, + ) + ); } /** @@ -88,12 +94,15 @@ private static function sanitize( $input, $valid_block_names ) { return $output; } - $output = array_intersect_key( $input, array_flip( self::ALLOWED_TOP_LEVEL_KEYS ) ); + $allowed_schema = self::get_allowed_schema(); + $allowed_top_level_keys = $allowed_schema['topLevel']; + $allowed_settings = $allowed_schema['settings']; + + $output = array_intersect_key( $input, array_flip( $allowed_top_level_keys ) ); // Build the schema. $schema = array(); $schema_settings_blocks = array(); - $allowed_settings = self::get_allowed_settings(); foreach ( $valid_block_names as $block ) { $schema_settings_blocks[ $block ] = $allowed_settings; } From 7df635f7e0786f0ca16297e745bdc2efe2b11eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 12:00:25 +0200 Subject: [PATCH 06/17] Only translate the stable settings for now --- src/wp-includes/theme-i18n.json | 57 +-------------------------------- 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/src/wp-includes/theme-i18n.json b/src/wp-includes/theme-i18n.json index 24c55c17cd665..a01a5aa566b4a 100644 --- a/src/wp-includes/theme-i18n.json +++ b/src/wp-includes/theme-i18n.json @@ -5,31 +5,6 @@ { "name": "Font size name" } - ], - "fontStyles": [ - { - "name": "Font style name" - } - ], - "fontWeights": [ - { - "name": "Font weight name" - } - ], - "fontFamilies": [ - { - "name": "Font family name" - } - ], - "textTransforms": [ - { - "name": "Text transform name" - } - ], - "textDecorations": [ - { - "name": "Text decoration name" - } ] }, "color": { @@ -56,31 +31,6 @@ { "name": "Font size name" } - ], - "fontStyles": [ - { - "name": "Font style name" - } - ], - "fontWeights": [ - { - "name": "Font weight name" - } - ], - "fontFamilies": [ - { - "name": "Font family name" - } - ], - "textTransforms": [ - { - "name": "Text transform name" - } - ], - "textDecorations": [ - { - "name": "Text decoration name" - } ] }, "color": { @@ -97,10 +47,5 @@ } } } - }, - "customTemplates": [ - { - "title": "Custom template name" - } - ] + } } From d1d805dd6066f637c006bc89a3a7644cf10b74c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 12:04:10 +0200 Subject: [PATCH 07/17] Only provide core defaults for the stable settings --- src/wp-includes/theme.json | 177 +++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 97 deletions(-) diff --git a/src/wp-includes/theme.json b/src/wp-includes/theme.json index d92547a146895..eafaf0d69b376 100644 --- a/src/wp-includes/theme.json +++ b/src/wp-includes/theme.json @@ -2,66 +2,48 @@ "version": 1, "settings": { "color": { - "palette": [ - { - "name": "Black", - "slug": "black", - "color": "#000000" - }, - { - "name": "Cyan bluish gray", - "slug": "cyan-bluish-gray", - "color": "#abb8c3" - }, - { - "name": "White", - "slug": "white", - "color": "#ffffff" - }, - { - "name": "Pale pink", - "slug": "pale-pink", - "color": "#f78da7" - }, + "custom": true, + "customGradient": true, + "duotone": [ { - "name": "Vivid red", - "slug": "vivid-red", - "color": "#cf2e2e" + "name": "Dark grayscale" , + "colors": [ "#000000", "#7f7f7f" ], + "slug": "dark-grayscale" }, { - "name": "Luminous vivid orange", - "slug": "luminous-vivid-orange", - "color": "#ff6900" + "name": "Grayscale" , + "colors": [ "#000000", "#ffffff" ], + "slug": "grayscale" }, { - "name": "Luminous vivid amber", - "slug": "luminous-vivid-amber", - "color": "#fcb900" + "name": "Purple and yellow" , + "colors": [ "#8c00b7", "#fcff41" ], + "slug": "purple-yellow" }, { - "name": "Light green cyan", - "slug": "light-green-cyan", - "color": "#7bdcb5" + "name": "Blue and red" , + "colors": [ "#000097", "#ff4747" ], + "slug": "blue-red" }, { - "name": "Vivid green cyan", - "slug": "vivid-green-cyan", - "color": "#00d084" + "name": "Midnight" , + "colors": [ "#000000", "#00a5ff" ], + "slug": "midnight" }, { - "name": "Pale cyan blue", - "slug": "pale-cyan-blue", - "color": "#8ed1fc" + "name": "Magenta and yellow" , + "colors": [ "#c7005a", "#fff278" ], + "slug": "magenta-yellow" }, { - "name": "Vivid cyan blue", - "slug": "vivid-cyan-blue", - "color": "#0693e3" + "name": "Purple and green" , + "colors": [ "#a60072", "#67ff66" ], + "slug": "purple-green" }, { - "name": "Vivid purple", - "slug": "vivid-purple", - "color": "#9b51e0" + "name": "Blue and orange" , + "colors": [ "#1900d8", "#ffa96b" ], + "slug": "blue-orange" } ], "gradients": [ @@ -126,60 +108,79 @@ "slug": "midnight" } ], - "duotone": [ + "link": false, + "palette": [ { - "name": "Dark grayscale" , - "colors": [ "#000000", "#7f7f7f" ], - "slug": "dark-grayscale" + "name": "Black", + "slug": "black", + "color": "#000000" }, { - "name": "Grayscale" , - "colors": [ "#000000", "#ffffff" ], - "slug": "grayscale" + "name": "Cyan bluish gray", + "slug": "cyan-bluish-gray", + "color": "#abb8c3" }, { - "name": "Purple and yellow" , - "colors": [ "#8c00b7", "#fcff41" ], - "slug": "purple-yellow" + "name": "White", + "slug": "white", + "color": "#ffffff" }, { - "name": "Blue and red" , - "colors": [ "#000097", "#ff4747" ], - "slug": "blue-red" + "name": "Pale pink", + "slug": "pale-pink", + "color": "#f78da7" }, { - "name": "Midnight" , - "colors": [ "#000000", "#00a5ff" ], - "slug": "midnight" + "name": "Vivid red", + "slug": "vivid-red", + "color": "#cf2e2e" }, { - "name": "Magenta and yellow" , - "colors": [ "#c7005a", "#fff278" ], - "slug": "magenta-yellow" + "name": "Luminous vivid orange", + "slug": "luminous-vivid-orange", + "color": "#ff6900" }, { - "name": "Purple and green" , - "colors": [ "#a60072", "#67ff66" ], - "slug": "purple-green" + "name": "Luminous vivid amber", + "slug": "luminous-vivid-amber", + "color": "#fcb900" }, { - "name": "Blue and orange" , - "colors": [ "#1900d8", "#ffa96b" ], - "slug": "blue-orange" + "name": "Light green cyan", + "slug": "light-green-cyan", + "color": "#7bdcb5" + }, + { + "name": "Vivid green cyan", + "slug": "vivid-green-cyan", + "color": "#00d084" + }, + { + "name": "Pale cyan blue", + "slug": "pale-cyan-blue", + "color": "#8ed1fc" + }, + { + "name": "Vivid cyan blue", + "slug": "vivid-cyan-blue", + "color": "#0693e3" + }, + { + "name": "Vivid purple", + "slug": "vivid-purple", + "color": "#9b51e0" } - ], - "custom": true, - "link": false, - "customGradient": true + ] + }, + "spacing": { + "customMargin": false, + "customPadding": false, + "units": [ "px", "em", "rem", "vh", "vw" ] }, "typography": { - "dropCap": true, "customFontSize": true, "customLineHeight": false, - "customFontStyle": true, - "customFontWeight": true, - "customTextTransforms": true, - "customTextDecorations": true, + "dropCap": true, "fontSizes": [ { "name": "Small", @@ -207,24 +208,6 @@ "size": "42px" } ] - }, - "spacing": { - "customMargin": false, - "customPadding": false, - "units": [ "px", "em", "rem", "vh", "vw" ] - }, - "border": { - "customColor": false, - "customRadius": false, - "customStyle": false, - "customWidth": false - }, - "blocks": { - "core/button": { - "border": { - "customRadius": true - } - } } } } From f3f11575359251d5e340e560ab7dc69adb6c622c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 12:21:28 +0200 Subject: [PATCH 08/17] Remove fallback for experimental-theme.json --- src/wp-includes/class-wp-theme-json-resolver.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 57c03a210b15d..7b034be66ee81 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -258,10 +258,6 @@ public static function get_core_data() { public static function get_theme_data( $theme_support_data = array() ) { if ( null === self::$theme ) { $theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'theme.json' ) ); - // Fallback to experimental-theme.json. - if ( empty( $theme_json_data ) ) { - $theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'experimental-theme.json' ) ); - } $theme_json_data = self::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); self::$theme = new WP_Theme_JSON( $theme_json_data ); } @@ -317,10 +313,6 @@ public static function get_merged_data( $settings = array() ) { public static function theme_has_support() { if ( ! isset( self::$theme_has_support ) ) { self::$theme_has_support = (bool) self::get_file_path_from_theme( 'theme.json' ); - if ( ! self::$theme_has_support ) { - // Fallback to experimental-theme.json. - self::$theme_has_support = (bool) self::get_file_path_from_theme( 'experimental-theme.json' ); - } } return self::$theme_has_support; From 7d8799f346dc3f1c63f86182fb98fb88ec71dc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 12:46:33 +0200 Subject: [PATCH 09/17] Calculate the allowed block names only once --- src/wp-includes/class-wp-theme-json.php | 32 +++++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index c599940fca8f3..eadc2b8848c92 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -18,6 +18,14 @@ class WP_Theme_JSON { */ private $theme_json = null; + /** + * Holds the allowed block names extracted from block.json. + * Shared among all instances so we only process it once. + * + * @var array + */ + private static $allowed_block_names = null; + const ALLOWED_TOP_LEVEL_KEYS = array( 'version', 'settings', @@ -60,8 +68,22 @@ public function __construct( $theme_json = array() ) { return; } - $valid_block_names = array_keys( WP_Block_Type_Registry::get_instance()->get_all_registered() ); - $this->theme_json = self::sanitize( $theme_json, $valid_block_names ); + $this->theme_json = self::sanitize( $theme_json ); + } + + /** + * Returns the allowed block names. + * + * @return array + */ + private static function get_allowed_block_names() { + if ( null !== self::$allowed_block_names ) { + return self::$allowed_block_names; + } + + self::$allowed_block_names = array_keys( WP_Block_Type_Registry::get_instance()->get_all_registered() ); + + return self::$allowed_block_names; } /** @@ -83,17 +105,17 @@ private static function get_allowed_schema() { * Sanitizes the input according to the schemas. * * @param array $input Structure to sanitize. - * @param array $valid_block_names List of valid block names. * * @return array The sanitized output. */ - private static function sanitize( $input, $valid_block_names ) { + private static function sanitize( $input ) { $output = array(); if ( ! is_array( $input ) ) { return $output; } + $allowed_blocks = self::get_allowed_block_names(); $allowed_schema = self::get_allowed_schema(); $allowed_top_level_keys = $allowed_schema['topLevel']; $allowed_settings = $allowed_schema['settings']; @@ -103,7 +125,7 @@ private static function sanitize( $input, $valid_block_names ) { // Build the schema. $schema = array(); $schema_settings_blocks = array(); - foreach ( $valid_block_names as $block ) { + foreach ( $allowed_blocks as $block ) { $schema_settings_blocks[ $block ] = $allowed_settings; } $schema['settings'] = $allowed_settings; From c6713490677931e353898707248833cc10c3ff48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 12:55:31 +0200 Subject: [PATCH 10/17] Calculate the allowed schema only once --- src/wp-includes/class-wp-theme-json.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index eadc2b8848c92..254ea0ac642a8 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -26,6 +26,13 @@ class WP_Theme_JSON { */ private static $allowed_block_names = null; + /** + * Holds the allowed schema. + * + * @var array + */ + private static $allowed_schema = null; + const ALLOWED_TOP_LEVEL_KEYS = array( 'version', 'settings', @@ -92,13 +99,19 @@ private static function get_allowed_block_names() { * @return array */ private static function get_allowed_schema() { - return apply_filters( + if ( null !== self::$allowed_schema ) { + return self::$allowed_schema; + } + + self::$allowed_schema = apply_filters( 'theme_json_allowed_schema', array( 'topLevel' => self::ALLOWED_TOP_LEVEL_KEYS, 'settings' => self::ALLOWED_SETTINGS, ) ); + + return self::$allowed_schema; } /** From fce4f7013cb1995d63f2f02d6c5b841c10d40494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 13:57:20 +0200 Subject: [PATCH 11/17] Allow core data to be filtered Because the data can be augmented (new settings, styles, etc) we need a mechanism to also set the values for this data. --- src/wp-includes/class-wp-theme-json-resolver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 7b034be66ee81..54e7299dc3205 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -234,6 +234,7 @@ public static function get_core_data() { } $config = self::read_json_file( __DIR__ . '/theme.json' ); + $config = apply_filters( 'theme_json_core_data', $config ); $config = self::translate( $config ); self::$core = new WP_Theme_JSON( $config ); From f02a359ed7b7711781ee7e831ac776bec4724001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 15:28:37 +0200 Subject: [PATCH 12/17] Fix tests --- .../class-wp-theme-json-resolver-test.php | 55 ------------------- .../tests/blocks/class-wp-theme-json-test.php | 6 -- 2 files changed, 61 deletions(-) diff --git a/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php b/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php index 4984fd1fdfd2f..22a657fa08468 100644 --- a/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php +++ b/tests/phpunit/tests/blocks/class-wp-theme-json-resolver-test.php @@ -49,31 +49,6 @@ function test_fields_are_extracted() { 'key' => 'name', 'context' => 'Font size name', ), - array( - 'path' => array( 'settings', 'typography', 'fontStyles' ), - 'key' => 'name', - 'context' => 'Font style name', - ), - array( - 'path' => array( 'settings', 'typography', 'fontWeights' ), - 'key' => 'name', - 'context' => 'Font weight name', - ), - array( - 'path' => array( 'settings', 'typography', 'fontFamilies' ), - 'key' => 'name', - 'context' => 'Font family name', - ), - array( - 'path' => array( 'settings', 'typography', 'textTransforms' ), - 'key' => 'name', - 'context' => 'Text transform name', - ), - array( - 'path' => array( 'settings', 'typography', 'textDecorations' ), - 'key' => 'name', - 'context' => 'Text decoration name', - ), array( 'path' => array( 'settings', 'color', 'palette' ), 'key' => 'name', @@ -94,31 +69,6 @@ function test_fields_are_extracted() { 'key' => 'name', 'context' => 'Font size name', ), - array( - 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontStyles' ), - 'key' => 'name', - 'context' => 'Font style name', - ), - array( - 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontWeights' ), - 'key' => 'name', - 'context' => 'Font weight name', - ), - array( - 'path' => array( 'settings', 'blocks', '*', 'typography', 'fontFamilies' ), - 'key' => 'name', - 'context' => 'Font family name', - ), - array( - 'path' => array( 'settings', 'blocks', '*', 'typography', 'textTransforms' ), - 'key' => 'name', - 'context' => 'Text transform name', - ), - array( - 'path' => array( 'settings', 'blocks', '*', 'typography', 'textDecorations' ), - 'key' => 'name', - 'context' => 'Text decoration name', - ), array( 'path' => array( 'settings', 'blocks', '*', 'color', 'palette' ), 'key' => 'name', @@ -129,11 +79,6 @@ function test_fields_are_extracted() { 'key' => 'name', 'context' => 'Gradient name', ), - array( - 'path' => array( 'customTemplates' ), - 'key' => 'title', - 'context' => 'Custom template name', - ), ); $this->assertEquals( $expected, $actual ); diff --git a/tests/phpunit/tests/blocks/class-wp-theme-json-test.php b/tests/phpunit/tests/blocks/class-wp-theme-json-test.php index 3ecb6061e6cd1..b654998763287 100644 --- a/tests/phpunit/tests/blocks/class-wp-theme-json-test.php +++ b/tests/phpunit/tests/blocks/class-wp-theme-json-test.php @@ -230,12 +230,6 @@ public function test_merge_incoming_data() { 'size' => 'fontSize', ), ), - 'fontFamilies' => array( - array( - 'slug' => 'fontFamily', - 'fontFamily' => 'fontFamily', - ), - ), ), 'blocks' => array( 'core/paragraph' => array( From f503e872e444fe9adfa9e6f26b60ae2495e5b52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 15:42:57 +0200 Subject: [PATCH 13/17] Fix linting issues --- src/wp-includes/class-wp-theme-json.php | 8 ++++---- tests/phpunit/tests/blocks/class-wp-theme-json-test.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 254ea0ac642a8..ed4c8f5512f62 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -55,10 +55,10 @@ class WP_Theme_JSON { 'units' => null, ), 'typography' => array( - 'customFontSize' => null, - 'customLineHeight' => null, - 'dropCap' => null, - 'fontSizes' => null, + 'customFontSize' => null, + 'customLineHeight'=> null, + 'dropCap' => null, + 'fontSizes' => null, ), ); diff --git a/tests/phpunit/tests/blocks/class-wp-theme-json-test.php b/tests/phpunit/tests/blocks/class-wp-theme-json-test.php index b654998763287..0f338e061ab42 100644 --- a/tests/phpunit/tests/blocks/class-wp-theme-json-test.php +++ b/tests/phpunit/tests/blocks/class-wp-theme-json-test.php @@ -224,7 +224,7 @@ public function test_merge_incoming_data() { ), ), 'typography' => array( - 'fontSizes' => array( + 'fontSizes' => array( array( 'slug' => 'fontSize', 'size' => 'fontSize', From b71eaa377dc72213d76256672403bffb8715fdc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Fri, 21 May 2021 15:47:28 +0200 Subject: [PATCH 14/17] Fix linting issues --- src/wp-includes/class-wp-theme-json.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index ed4c8f5512f62..42c67858ca525 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -55,10 +55,10 @@ class WP_Theme_JSON { 'units' => null, ), 'typography' => array( - 'customFontSize' => null, - 'customLineHeight'=> null, - 'dropCap' => null, - 'fontSizes' => null, + 'customFontSize' => null, + 'customLineHeight' => null, + 'dropCap' => null, + 'fontSizes' => null, ), ); From d4a16a3696c9c80d87d336b961378010edb74fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 24 May 2021 09:33:00 +0200 Subject: [PATCH 15/17] Unset features that are already part of the store --- src/wp-includes/block-editor.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 8d85cfe5d0ae2..24a2ccdfad1b2 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -248,24 +248,31 @@ function get_block_editor_settings( $editor_name, $custom_settings = array() ) { // These settings may need to be updated based on data coming from theme.json sources. if ( isset( $editor_settings['__experimentalFeatures']['color']['palette'] ) ) { $editor_settings['colors'] = $editor_settings['__experimentalFeatures']['color']['palette']; + unset( $editor_settings['__experimentalFeatures']['color']['palette'] ); } if ( isset( $editor_settings['__experimentalFeatures']['color']['gradients'] ) ) { $editor_settings['gradients'] = $editor_settings['__experimentalFeatures']['color']['gradients']; + unset( $editor_settings['__experimentalFeatures']['color']['gradients'] ); } if ( isset( $editor_settings['__experimentalFeatures']['color']['custom'] ) ) { $editor_settings['disableCustomColors'] = $editor_settings['__experimentalFeatures']['color']['custom']; + unset( $editor_settings['__experimentalFeatures']['color']['custom'] ); } if ( isset( $editor_settings['__experimentalFeatures']['color']['customGradient'] ) ) { $editor_settings['disableCustomGradients'] = $editor_settings['__experimentalFeatures']['color']['customGradient']; + unset( $editor_settings['__experimentalFeatures']['color']['customGradient'] ); } if ( isset( $editor_settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { $editor_settings['fontSizes'] = $editor_settings['__experimentalFeatures']['typography']['fontSizes']; + unset( $editor_settings['__experimentalFeatures']['typography']['fontSizes'] ); } if ( isset( $editor_settings['__experimentalFeatures']['typography']['customFontSize'] ) ) { $editor_settings['disableCustomFontSizes'] = $editor_settings['__experimentalFeatures']['typography']['customFontSize']; + unset( $editor_settings['__experimentalFeatures']['typography']['customFontSize'] ); } if ( isset( $editor_settings['__experimentalFeatures']['typography']['customLineHeight'] ) ) { $editor_settings['enableCustomLineHeight'] = $editor_settings['__experimentalFeatures']['typography']['customLineHeight']; + unset( $editor_settings['__experimentalFeatures']['typography']['customLineHeight'] ); } if ( isset( $editor_settings['__experimentalFeatures']['spacing']['units'] ) ) { if ( ! is_array( $editor_settings['__experimentalFeatures']['spacing']['units'] ) ) { @@ -273,9 +280,11 @@ function get_block_editor_settings( $editor_name, $custom_settings = array() ) { } else { $editor_settings['enableCustomUnits'] = count( $editor_settings['__experimentalFeatures']['spacing']['units'] ) > 0; } + unset( $editor_settings['__experimentalFeatures']['spacing']['units'] ); } if ( isset( $editor_settings['__experimentalFeatures']['spacing']['customPadding'] ) ) { $editor_settings['enableCustomSpacing'] = $editor_settings['__experimentalFeatures']['spacing']['customPadding']; + unset( $editor_settings['__experimentalFeatures']['spacing']['customPadding'] ); } /** From eb50ac508cf16439f05183ab0227a17ce6eb0e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 24 May 2021 09:40:32 +0200 Subject: [PATCH 16/17] Remove filters to hook into theme_json_* processing --- .../class-wp-theme-json-resolver.php | 1 - src/wp-includes/class-wp-theme-json.php | 39 ++----------------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 54e7299dc3205..7b034be66ee81 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -234,7 +234,6 @@ public static function get_core_data() { } $config = self::read_json_file( __DIR__ . '/theme.json' ); - $config = apply_filters( 'theme_json_core_data', $config ); $config = self::translate( $config ); self::$core = new WP_Theme_JSON( $config ); diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 42c67858ca525..d3ac62bebc11f 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -26,13 +26,6 @@ class WP_Theme_JSON { */ private static $allowed_block_names = null; - /** - * Holds the allowed schema. - * - * @var array - */ - private static $allowed_schema = null; - const ALLOWED_TOP_LEVEL_KEYS = array( 'version', 'settings', @@ -93,27 +86,6 @@ private static function get_allowed_block_names() { return self::$allowed_block_names; } - /** - * Returns the allowed schema for a theme.json structure. - * - * @return array - */ - private static function get_allowed_schema() { - if ( null !== self::$allowed_schema ) { - return self::$allowed_schema; - } - - self::$allowed_schema = apply_filters( - 'theme_json_allowed_schema', - array( - 'topLevel' => self::ALLOWED_TOP_LEVEL_KEYS, - 'settings' => self::ALLOWED_SETTINGS, - ) - ); - - return self::$allowed_schema; - } - /** * Sanitizes the input according to the schemas. * @@ -128,20 +100,17 @@ private static function sanitize( $input ) { return $output; } - $allowed_blocks = self::get_allowed_block_names(); - $allowed_schema = self::get_allowed_schema(); - $allowed_top_level_keys = $allowed_schema['topLevel']; - $allowed_settings = $allowed_schema['settings']; + $allowed_blocks = self::get_allowed_block_names(); - $output = array_intersect_key( $input, array_flip( $allowed_top_level_keys ) ); + $output = array_intersect_key( $input, array_flip( self::ALLOWED_TOP_LEVEL_KEYS ) ); // Build the schema. $schema = array(); $schema_settings_blocks = array(); foreach ( $allowed_blocks as $block ) { - $schema_settings_blocks[ $block ] = $allowed_settings; + $schema_settings_blocks[ $block ] = self::ALLOWED_SETTINGS; } - $schema['settings'] = $allowed_settings; + $schema['settings'] = self::ALLOWED_SETTINGS; $schema['settings']['blocks'] = $schema_settings_blocks; // Remove anything that's not present in the schema. From 665801c91d9c844dca6cdd77e814bde64bc862fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 24 May 2021 09:50:14 +0200 Subject: [PATCH 17/17] Mark the classes as private --- src/wp-includes/class-wp-theme-json-resolver.php | 2 ++ src/wp-includes/class-wp-theme-json.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 7b034be66ee81..87e2fa4bf150f 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -9,6 +9,8 @@ /** * Class that abstracts the processing * of the different data sources. + * + * @access private */ class WP_Theme_JSON_Resolver { diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index d3ac62bebc11f..b78f7c9be41f8 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -8,6 +8,8 @@ /** * Class that encapsulates the processing of * structures that adhere to the theme.json spec. + * + * @access private */ class WP_Theme_JSON {