true ) ); if ( is_array( $decoded_file ) ) { $config = $decoded_file; } } return $config; } /** * Returns a data structure used in theme.json translation. * * @since 5.8.0 * @deprecated 5.9.0 * * @return array An array of theme.json fields that are translatable and the keys that are translatable. */ public static function get_fields_to_translate() { _deprecated_function( __METHOD__, '5.9.0' ); return array(); } /** * 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. * * @since 5.8.0 * * @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' ) { if ( null === self::$i18n_schema ) { $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); self::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; } return translate_settings_using_i18n_schema( self::$i18n_schema, $theme_json, $domain ); } /** * Return core's origin config. * * @since 5.8.0 * * @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, 'default' ); return self::$core; } /** * Returns the theme's data. * * Data from theme.json will be backfilled from existing * theme supports, if any. Note that if the same data * is present in theme.json and in theme supports, * the theme.json takes precendence. * * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * * @param array $deprecated Deprecated. Not used. * @return WP_Theme_JSON Entity that holds theme data. */ public static function get_theme_data( $deprecated = array() ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __METHOD__, '5.9.0' ); } if ( null === self::$theme ) { $theme_json_data = self::read_json_file( self::get_file_path_from_theme( '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 ( wp_get_theme()->parent() ) { // Get parent theme.json. $parent_theme_json_data = self::read_json_file( self::get_file_path_from_theme( 'theme.json', true ) ); $parent_theme_json_data = self::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); $parent_theme = new WP_Theme_JSON( $parent_theme_json_data ); // Merge the child theme.json into the parent theme.json. // The child theme takes precedence over the parent. $parent_theme->merge( self::$theme ); self::$theme = $parent_theme; } } /* * We want the presets and settings declared in theme.json * to override the ones declared via theme supports. * So we take theme supports, transform it to theme.json shape * and merge the self::$theme upon that. */ $theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_default_block_editor_settings() ); if ( ! self::theme_has_support() ) { if ( ! isset( $theme_support_data['settings']['color'] ) ) { $theme_support_data['settings']['color'] = array(); } $default_palette = false; if ( current_theme_supports( 'default-color-palette' ) ) { $default_palette = true; } if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { // If the theme does not have any palette, we still want to show the core one. $default_palette = true; } $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; $default_gradients = false; if ( current_theme_supports( 'default-gradient-presets' ) ) { $default_gradients = true; } if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { // If the theme does not have any gradients, we still want to show the core ones. $default_gradients = true; } $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; } $with_theme_supports = new WP_Theme_JSON( $theme_support_data ); $with_theme_supports->merge( self::$theme ); return $with_theme_supports; } /** * Returns the custom post type that contains the user's origin config * for the current theme or a void array if none are found. * * This can also create and return a new draft custom post type. * * @since 5.9.0 * * @param WP_Theme $theme The theme object. If empty, it * defaults to the current theme. * @param bool $should_create_cpt Optional. Whether a new custom post * type should be created if none are * found. Default false. * @param array $post_status_filter Optional. Filter custom post type by * post status. Default `array( 'publish' )`, * so it only fetches published posts. * @return array Custom Post Type for the user's origin config. */ public static function get_user_data_from_custom_post_type( $theme, $should_create_cpt = false, $post_status_filter = array( 'publish' ) ) { if ( ! $theme instanceof WP_Theme ) { $theme = wp_get_theme(); } $user_cpt = array(); $post_type_filter = 'wp_global_styles'; $args = array( 'numberposts' => 1, 'orderby' => 'date', 'order' => 'desc', 'post_type' => $post_type_filter, 'post_status' => $post_status_filter, 'tax_query' => array( array( 'taxonomy' => 'wp_theme', 'field' => 'name', 'terms' => $theme->get_stylesheet(), ), ), ); $cache_key = sprintf( 'wp_global_styles_%s', md5( serialize( $args ) ) ); $post_id = wp_cache_get( $cache_key ); if ( (int) $post_id > 0 ) { return get_post( $post_id, ARRAY_A ); } // Special case: '-1' is a results not found. if ( -1 === $post_id && ! $should_create_cpt ) { return $user_cpt; } $recent_posts = wp_get_recent_posts( $args ); if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { $user_cpt = $recent_posts[0]; } elseif ( $should_create_cpt ) { $cpt_post_id = wp_insert_post( array( 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', 'post_status' => 'publish', 'post_title' => 'Custom Styles', 'post_type' => $post_type_filter, 'post_name' => 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() ), 'tax_input' => array( 'wp_theme' => array( wp_get_theme()->get_stylesheet() ), ), ), true ); $user_cpt = get_post( $cpt_post_id, ARRAY_A ); } $cache_expiration = $user_cpt ? DAY_IN_SECONDS : HOUR_IN_SECONDS; wp_cache_set( $cache_key, $user_cpt ? $user_cpt['ID'] : -1, '', $cache_expiration ); return $user_cpt; } /** * Returns the user's origin config. * * @since 5.9.0 * * @return WP_Theme_JSON Entity that holds user data. */ public static function get_user_data() { if ( null !== self::$user ) { return self::$user; } $config = array(); $user_cpt = self::get_user_data_from_custom_post_type( wp_get_theme() ); if ( array_key_exists( 'post_content', $user_cpt ) ) { $decoded_data = json_decode( $user_cpt['post_content'], true ); $json_decoding_error = json_last_error(); if ( JSON_ERROR_NONE !== $json_decoding_error ) { trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); return new WP_Theme_JSON( $config, 'custom' ); } // Very important to verify if the flag isGlobalStylesUserThemeJSON is true. // If is not true the content was not escaped and is not safe. if ( is_array( $decoded_data ) && isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && $decoded_data['isGlobalStylesUserThemeJSON'] ) { unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); $config = $decoded_data; } } self::$user = new WP_Theme_JSON( $config, 'custom' ); return self::$user; } /** * There are three sources of data (origins) for a site: * default, theme, and custom. The custom's has higher priority * than the theme's, and the theme's higher than default's. * * Unlike the getters {@link get_core_data}, * {@link get_theme_data}, and {@link get_user_data}, * this method returns data after it has been merged * with the previous origins. This means that if the same piece of data * is declared in different origins (user, theme, and core), * the last origin overrides the previous. * * For example, if the user has set a background color * for the paragraph block, and the theme has done it as well, * the user preference wins. * * @since 5.8.0 * @since 5.9.0 Added user data, removed the `$settings` parameter, * added the `$origin` parameter. * * @param string $origin Optional. To what level should we merge data. * Valid values are 'theme' or 'custom'. Default 'custom'. * @return WP_Theme_JSON */ public static function get_merged_data( $origin = 'custom' ) { if ( is_array( $origin ) ) { _deprecated_argument( __FUNCTION__, '5.9.0' ); } $result = new WP_Theme_JSON(); $result->merge( self::get_core_data() ); $result->merge( self::get_theme_data() ); if ( 'custom' === $origin ) { $result->merge( self::get_user_data() ); } return $result; } /** * Returns the ID of the custom post type * that stores user data. * * @since 5.9.0 * * @return integer|null */ public static function get_user_custom_post_type_id() { if ( null !== self::$user_custom_post_type_id ) { return self::$user_custom_post_type_id; } $user_cpt = self::get_user_data_from_custom_post_type( wp_get_theme(), true ); if ( array_key_exists( 'ID', $user_cpt ) ) { self::$user_custom_post_type_id = $user_cpt['ID']; } return self::$user_custom_post_type_id; } /** * Whether the current theme has a theme.json file. * * @since 5.8.0 * @since 5.9.0 Added a check in the parent theme. * * @return bool */ public static function theme_has_support() { if ( ! isset( self::$theme_has_support ) ) { self::$theme_has_support = ( is_readable( self::get_file_path_from_theme( 'theme.json' ) ) || is_readable( self::get_file_path_from_theme( 'theme.json', true ) ) ); } 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. * * @since 5.8.0 * @since 5.9.0 Adapted to work with child themes, added the `$template` argument. * * @param string $file_name Name of the file. * @param bool $template Optional. Use template theme directory. Default false. * @return string The whole file path or empty if the file doesn't exist. */ private static function get_file_path_from_theme( $file_name, $template = false ) { $path = $template ? get_template_directory() : get_stylesheet_directory(); $candidate = $path . '/' . $file_name; return is_readable( $candidate ) ? $candidate : ''; } /** * Cleans the cached data so it can be recalculated. * * @since 5.8.0 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`, * and `$i18n_schema` variables to reset. */ public static function clean_cached_data() { self::$core = null; self::$theme = null; self::$user = null; self::$user_custom_post_type_id = null; self::$theme_has_support = null; self::$i18n_schema = null; } }