[ Index ] |
PHP Cross Reference of WordPress |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WP_Theme_JSON_Resolver class 4 * 5 * @package WordPress 6 * @subpackage Theme 7 * @since 5.8.0 8 */ 9 10 /** 11 * Class that abstracts the processing of the different data sources 12 * for site-level config and offers an API to work with them. 13 * 14 * This class is for internal core usage and is not supposed to be used by extenders (plugins and/or themes). 15 * This is a low-level API that may need to do breaking changes. Please, 16 * use get_global_settings, get_global_styles, and get_global_stylesheet instead. 17 * 18 * @access private 19 */ 20 class WP_Theme_JSON_Resolver { 21 22 /** 23 * Container for data coming from core. 24 * 25 * @since 5.8.0 26 * @var WP_Theme_JSON 27 */ 28 protected static $core = null; 29 30 /** 31 * Container for data coming from the theme. 32 * 33 * @since 5.8.0 34 * @var WP_Theme_JSON 35 */ 36 protected static $theme = null; 37 38 /** 39 * Whether or not the theme supports theme.json. 40 * 41 * @since 5.8.0 42 * @var bool 43 */ 44 protected static $theme_has_support = null; 45 46 /** 47 * Container for data coming from the user. 48 * 49 * @since 5.9.0 50 * @var WP_Theme_JSON 51 */ 52 protected static $user = null; 53 54 /** 55 * Stores the ID of the custom post type 56 * that holds the user data. 57 * 58 * @since 5.9.0 59 * @var int 60 */ 61 protected static $user_custom_post_type_id = null; 62 63 /** 64 * Container to keep loaded i18n schema for `theme.json`. 65 * 66 * @since 5.8.0 As `$theme_json_i18n`. 67 * @since 5.9.0 Renamed from `$theme_json_i18n` to `$i18n_schema`. 68 * @var array 69 */ 70 protected static $i18n_schema = null; 71 72 /** 73 * Processes a file that adheres to the theme.json schema 74 * and returns an array with its contents, or a void array if none found. 75 * 76 * @since 5.8.0 77 * 78 * @param string $file_path Path to file. Empty if no file. 79 * @return array Contents that adhere to the theme.json schema. 80 */ 81 protected static function read_json_file( $file_path ) { 82 $config = array(); 83 if ( $file_path ) { 84 $decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) ); 85 if ( is_array( $decoded_file ) ) { 86 $config = $decoded_file; 87 } 88 } 89 return $config; 90 } 91 92 /** 93 * Returns a data structure used in theme.json translation. 94 * 95 * @since 5.8.0 96 * @deprecated 5.9.0 97 * 98 * @return array An array of theme.json fields that are translatable and the keys that are translatable. 99 */ 100 public static function get_fields_to_translate() { 101 _deprecated_function( __METHOD__, '5.9.0' ); 102 return array(); 103 } 104 105 /** 106 * Given a theme.json structure modifies it in place to update certain values 107 * by its translated strings according to the language set by the user. 108 * 109 * @since 5.8.0 110 * 111 * @param array $theme_json The theme.json to translate. 112 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. 113 * Default 'default'. 114 * @return array Returns the modified $theme_json_structure. 115 */ 116 protected static function translate( $theme_json, $domain = 'default' ) { 117 if ( null === static::$i18n_schema ) { 118 $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); 119 static::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; 120 } 121 122 return translate_settings_using_i18n_schema( static::$i18n_schema, $theme_json, $domain ); 123 } 124 125 /** 126 * Returns core's origin config. 127 * 128 * @since 5.8.0 129 * 130 * @return WP_Theme_JSON Entity that holds core data. 131 */ 132 public static function get_core_data() { 133 if ( null !== static::$core ) { 134 return static::$core; 135 } 136 137 $config = static::read_json_file( __DIR__ . '/theme.json' ); 138 $config = static::translate( $config ); 139 static::$core = new WP_Theme_JSON( $config, 'default' ); 140 141 return static::$core; 142 } 143 144 /** 145 * Returns the theme's data. 146 * 147 * Data from theme.json will be backfilled from existing 148 * theme supports, if any. Note that if the same data 149 * is present in theme.json and in theme supports, 150 * the theme.json takes precedence. 151 * 152 * @since 5.8.0 153 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. 154 * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. 155 * 156 * @param array $deprecated Deprecated. Not used. 157 * @param array $options { 158 * Options arguments. 159 * 160 * @type bool $with_supports Whether to include theme supports in the data. Default true. 161 * } 162 * @return WP_Theme_JSON Entity that holds theme data. 163 */ 164 public static function get_theme_data( $deprecated = array(), $options = array() ) { 165 if ( ! empty( $deprecated ) ) { 166 _deprecated_argument( __METHOD__, '5.9.0' ); 167 } 168 169 $options = wp_parse_args( $options, array( 'with_supports' => true ) ); 170 171 if ( null === static::$theme ) { 172 $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) ); 173 $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); 174 static::$theme = new WP_Theme_JSON( $theme_json_data ); 175 176 if ( wp_get_theme()->parent() ) { 177 // Get parent theme.json. 178 $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) ); 179 $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); 180 $parent_theme = new WP_Theme_JSON( $parent_theme_json_data ); 181 182 // Merge the child theme.json into the parent theme.json. 183 // The child theme takes precedence over the parent. 184 $parent_theme->merge( static::$theme ); 185 static::$theme = $parent_theme; 186 } 187 } 188 189 if ( ! $options['with_supports'] ) { 190 return static::$theme; 191 } 192 193 /* 194 * We want the presets and settings declared in theme.json 195 * to override the ones declared via theme supports. 196 * So we take theme supports, transform it to theme.json shape 197 * and merge the static::$theme upon that. 198 */ 199 $theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_default_block_editor_settings() ); 200 if ( ! static::theme_has_support() ) { 201 if ( ! isset( $theme_support_data['settings']['color'] ) ) { 202 $theme_support_data['settings']['color'] = array(); 203 } 204 205 $default_palette = false; 206 if ( current_theme_supports( 'default-color-palette' ) ) { 207 $default_palette = true; 208 } 209 if ( ! isset( $theme_support_data['settings']['color']['palette'] ) ) { 210 // If the theme does not have any palette, we still want to show the core one. 211 $default_palette = true; 212 } 213 $theme_support_data['settings']['color']['defaultPalette'] = $default_palette; 214 215 $default_gradients = false; 216 if ( current_theme_supports( 'default-gradient-presets' ) ) { 217 $default_gradients = true; 218 } 219 if ( ! isset( $theme_support_data['settings']['color']['gradients'] ) ) { 220 // If the theme does not have any gradients, we still want to show the core ones. 221 $default_gradients = true; 222 } 223 $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; 224 225 // Classic themes without a theme.json don't support global duotone. 226 $theme_support_data['settings']['color']['defaultDuotone'] = false; 227 } 228 $with_theme_supports = new WP_Theme_JSON( $theme_support_data ); 229 $with_theme_supports->merge( static::$theme ); 230 231 return $with_theme_supports; 232 } 233 234 /** 235 * Returns the custom post type that contains the user's origin config 236 * for the active theme or a void array if none are found. 237 * 238 * This can also create and return a new draft custom post type. 239 * 240 * @since 5.9.0 241 * 242 * @param WP_Theme $theme The theme object. If empty, it 243 * defaults to the active theme. 244 * @param bool $create_post Optional. Whether a new custom post 245 * type should be created if none are 246 * found. Default false. 247 * @param array $post_status_filter Optional. Filter custom post type by 248 * post status. Default `array( 'publish' )`, 249 * so it only fetches published posts. 250 * @return array Custom Post Type for the user's origin config. 251 */ 252 public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) { 253 if ( ! $theme instanceof WP_Theme ) { 254 $theme = wp_get_theme(); 255 } 256 $user_cpt = array(); 257 $post_type_filter = 'wp_global_styles'; 258 $args = array( 259 'numberposts' => 1, 260 'orderby' => 'date', 261 'order' => 'desc', 262 'post_type' => $post_type_filter, 263 'post_status' => $post_status_filter, 264 'tax_query' => array( 265 array( 266 'taxonomy' => 'wp_theme', 267 'field' => 'name', 268 'terms' => $theme->get_stylesheet(), 269 ), 270 ), 271 ); 272 273 $cache_key = sprintf( 'wp_global_styles_%s', md5( serialize( $args ) ) ); 274 $post_id = wp_cache_get( $cache_key ); 275 276 if ( (int) $post_id > 0 ) { 277 return get_post( $post_id, ARRAY_A ); 278 } 279 280 // Special case: '-1' is a results not found. 281 if ( -1 === $post_id && ! $create_post ) { 282 return $user_cpt; 283 } 284 285 $recent_posts = wp_get_recent_posts( $args ); 286 if ( is_array( $recent_posts ) && ( count( $recent_posts ) === 1 ) ) { 287 $user_cpt = $recent_posts[0]; 288 } elseif ( $create_post ) { 289 $cpt_post_id = wp_insert_post( 290 array( 291 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', 292 'post_status' => 'publish', 293 'post_title' => 'Custom Styles', 294 'post_type' => $post_type_filter, 295 'post_name' => 'wp-global-styles-' . urlencode( wp_get_theme()->get_stylesheet() ), 296 'tax_input' => array( 297 'wp_theme' => array( wp_get_theme()->get_stylesheet() ), 298 ), 299 ), 300 true 301 ); 302 $user_cpt = get_post( $cpt_post_id, ARRAY_A ); 303 } 304 $cache_expiration = $user_cpt ? DAY_IN_SECONDS : HOUR_IN_SECONDS; 305 wp_cache_set( $cache_key, $user_cpt ? $user_cpt['ID'] : -1, '', $cache_expiration ); 306 307 return $user_cpt; 308 } 309 310 /** 311 * Returns the user's origin config. 312 * 313 * @since 5.9.0 314 * 315 * @return WP_Theme_JSON Entity that holds styles for user data. 316 */ 317 public static function get_user_data() { 318 if ( null !== static::$user ) { 319 return static::$user; 320 } 321 322 $config = array(); 323 $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() ); 324 325 if ( array_key_exists( 'post_content', $user_cpt ) ) { 326 $decoded_data = json_decode( $user_cpt['post_content'], true ); 327 328 $json_decoding_error = json_last_error(); 329 if ( JSON_ERROR_NONE !== $json_decoding_error ) { 330 trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); 331 return new WP_Theme_JSON( $config, 'custom' ); 332 } 333 334 // Very important to verify that the flag isGlobalStylesUserThemeJSON is true. 335 // If it's not true then the content was not escaped and is not safe. 336 if ( 337 is_array( $decoded_data ) && 338 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && 339 $decoded_data['isGlobalStylesUserThemeJSON'] 340 ) { 341 unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); 342 $config = $decoded_data; 343 } 344 } 345 static::$user = new WP_Theme_JSON( $config, 'custom' ); 346 347 return static::$user; 348 } 349 350 /** 351 * Returns the data merged from multiple origins. 352 * 353 * There are three sources of data (origins) for a site: 354 * default, theme, and custom. The custom's has higher priority 355 * than the theme's, and the theme's higher than default's. 356 * 357 * Unlike the getters 358 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data}, 359 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data}, 360 * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data}, 361 * this method returns data after it has been merged with the previous origins. 362 * This means that if the same piece of data is declared in different origins 363 * (user, theme, and core), the last origin overrides the previous. 364 * 365 * For example, if the user has set a background color 366 * for the paragraph block, and the theme has done it as well, 367 * the user preference wins. 368 * 369 * @since 5.8.0 370 * @since 5.9.0 Added user data, removed the `$settings` parameter, 371 * added the `$origin` parameter. 372 * 373 * @param string $origin Optional. To what level should we merge data. 374 * Valid values are 'theme' or 'custom'. Default 'custom'. 375 * @return WP_Theme_JSON 376 */ 377 public static function get_merged_data( $origin = 'custom' ) { 378 if ( is_array( $origin ) ) { 379 _deprecated_argument( __FUNCTION__, '5.9.0' ); 380 } 381 382 $result = new WP_Theme_JSON(); 383 $result->merge( static::get_core_data() ); 384 $result->merge( static::get_theme_data() ); 385 386 if ( 'custom' === $origin ) { 387 $result->merge( static::get_user_data() ); 388 } 389 390 return $result; 391 } 392 393 /** 394 * Returns the ID of the custom post type 395 * that stores user data. 396 * 397 * @since 5.9.0 398 * 399 * @return integer|null 400 */ 401 public static function get_user_global_styles_post_id() { 402 if ( null !== static::$user_custom_post_type_id ) { 403 return static::$user_custom_post_type_id; 404 } 405 406 $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme(), true ); 407 408 if ( array_key_exists( 'ID', $user_cpt ) ) { 409 static::$user_custom_post_type_id = $user_cpt['ID']; 410 } 411 412 return static::$user_custom_post_type_id; 413 } 414 415 /** 416 * Determines whether the active theme has a theme.json file. 417 * 418 * @since 5.8.0 419 * @since 5.9.0 Added a check in the parent theme. 420 * 421 * @return bool 422 */ 423 public static function theme_has_support() { 424 if ( ! isset( static::$theme_has_support ) ) { 425 static::$theme_has_support = ( 426 is_readable( static::get_file_path_from_theme( 'theme.json' ) ) || 427 is_readable( static::get_file_path_from_theme( 'theme.json', true ) ) 428 ); 429 } 430 431 return static::$theme_has_support; 432 } 433 434 /** 435 * Builds the path to the given file and checks that it is readable. 436 * 437 * If it isn't, returns an empty string, otherwise returns the whole file path. 438 * 439 * @since 5.8.0 440 * @since 5.9.0 Adapted to work with child themes, added the `$template` argument. 441 * 442 * @param string $file_name Name of the file. 443 * @param bool $template Optional. Use template theme directory. Default false. 444 * @return string The whole file path or empty if the file doesn't exist. 445 */ 446 protected static function get_file_path_from_theme( $file_name, $template = false ) { 447 $path = $template ? get_template_directory() : get_stylesheet_directory(); 448 $candidate = $path . '/' . $file_name; 449 450 return is_readable( $candidate ) ? $candidate : ''; 451 } 452 453 /** 454 * Cleans the cached data so it can be recalculated. 455 * 456 * @since 5.8.0 457 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`, 458 * and `$i18n_schema` variables to reset. 459 */ 460 public static function clean_cached_data() { 461 static::$core = null; 462 static::$theme = null; 463 static::$user = null; 464 static::$user_custom_post_type_id = null; 465 static::$theme_has_support = null; 466 static::$i18n_schema = null; 467 } 468 469 /** 470 * Returns the style variations defined by the theme. 471 * 472 * @since 6.0.0 473 * 474 * @return array 475 */ 476 public static function get_style_variations() { 477 $variations = array(); 478 $base_directory = get_stylesheet_directory() . '/styles'; 479 if ( is_dir( $base_directory ) ) { 480 $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); 481 $nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); 482 ksort( $nested_html_files ); 483 foreach ( $nested_html_files as $path => $file ) { 484 $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); 485 if ( is_array( $decoded_file ) ) { 486 $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); 487 $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); 488 if ( empty( $variation['title'] ) ) { 489 $variation['title'] = basename( $path, '.json' ); 490 } 491 $variations[] = $variation; 492 } 493 } 494 } 495 return $variations; 496 } 497 498 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:03 2024 | Cross-referenced by PHPXref 0.7.1 |