| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[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 #[AllowDynamicProperties] 21 class WP_Theme_JSON_Resolver { 22 23 /** 24 * Container for keep track of registered blocks. 25 * 26 * @since 6.1.0 27 * @var array 28 */ 29 protected static $blocks_cache = array( 30 'core' => array(), 31 'blocks' => array(), 32 'theme' => array(), 33 'user' => array(), 34 ); 35 36 /** 37 * Container for data coming from core. 38 * 39 * @since 5.8.0 40 * @var WP_Theme_JSON 41 */ 42 protected static $core = null; 43 44 /** 45 * Container for data coming from the blocks. 46 * 47 * @since 6.1.0 48 * @var WP_Theme_JSON 49 */ 50 protected static $blocks = null; 51 52 /** 53 * Container for data coming from the theme. 54 * 55 * @since 5.8.0 56 * @var WP_Theme_JSON 57 */ 58 protected static $theme = null; 59 60 /** 61 * Container for data coming from the user. 62 * 63 * @since 5.9.0 64 * @var WP_Theme_JSON 65 */ 66 protected static $user = null; 67 68 /** 69 * Stores the ID of the custom post type 70 * that holds the user data. 71 * 72 * @since 5.9.0 73 * @var int 74 */ 75 protected static $user_custom_post_type_id = null; 76 77 /** 78 * Container to keep loaded i18n schema for `theme.json`. 79 * 80 * @since 5.8.0 As `$theme_json_i18n`. 81 * @since 5.9.0 Renamed from `$theme_json_i18n` to `$i18n_schema`. 82 * @var array 83 */ 84 protected static $i18n_schema = null; 85 86 /** 87 * `theme.json` file cache. 88 * 89 * @since 6.1.0 90 * @var array 91 */ 92 protected static $theme_json_file_cache = array(); 93 94 /** 95 * Processes a file that adheres to the theme.json schema 96 * and returns an array with its contents, or a void array if none found. 97 * 98 * @since 5.8.0 99 * @since 6.1.0 Added caching. 100 * 101 * @param string $file_path Path to file. Empty if no file. 102 * @return array Contents that adhere to the theme.json schema. 103 */ 104 protected static function read_json_file( $file_path ) { 105 if ( $file_path ) { 106 if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) { 107 return static::$theme_json_file_cache[ $file_path ]; 108 } 109 110 $decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) ); 111 if ( is_array( $decoded_file ) ) { 112 static::$theme_json_file_cache[ $file_path ] = $decoded_file; 113 return static::$theme_json_file_cache[ $file_path ]; 114 } 115 } 116 117 return array(); 118 } 119 120 /** 121 * Returns a data structure used in theme.json translation. 122 * 123 * @since 5.8.0 124 * @deprecated 5.9.0 125 * 126 * @return array An array of theme.json fields that are translatable and the keys that are translatable. 127 */ 128 public static function get_fields_to_translate() { 129 _deprecated_function( __METHOD__, '5.9.0' ); 130 return array(); 131 } 132 133 /** 134 * Given a theme.json structure modifies it in place to update certain values 135 * by its translated strings according to the language set by the user. 136 * 137 * @since 5.8.0 138 * 139 * @param array $theme_json The theme.json to translate. 140 * @param string $domain Optional. Text domain. Unique identifier for retrieving translated strings. 141 * Default 'default'. 142 * @return array Returns the modified $theme_json_structure. 143 */ 144 protected static function translate( $theme_json, $domain = 'default' ) { 145 if ( null === static::$i18n_schema ) { 146 $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); 147 static::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; 148 } 149 150 return translate_settings_using_i18n_schema( static::$i18n_schema, $theme_json, $domain ); 151 } 152 153 /** 154 * Returns core's origin config. 155 * 156 * @since 5.8.0 157 * 158 * @return WP_Theme_JSON Entity that holds core data. 159 */ 160 public static function get_core_data() { 161 if ( null !== static::$core && static::has_same_registered_blocks( 'core' ) ) { 162 return static::$core; 163 } 164 165 $config = static::read_json_file( __DIR__ . '/theme.json' ); 166 $config = static::translate( $config ); 167 168 /** 169 * Filters the default data provided by WordPress for global styles & settings. 170 * 171 * @since 6.1.0 172 * 173 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. 174 */ 175 $theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data( $config, 'default' ) ); 176 177 /* 178 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 179 * compatible class that is not a WP_Theme_JSON_Data object. 180 */ 181 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 182 static::$core = $theme_json->get_theme_json(); 183 } else { 184 $config = $theme_json->get_data(); 185 static::$core = new WP_Theme_JSON( $config, 'default' ); 186 } 187 188 return static::$core; 189 } 190 191 /** 192 * Checks whether the registered blocks were already processed for this origin. 193 * 194 * @since 6.1.0 195 * 196 * @param string $origin Data source for which to cache the blocks. 197 * Valid values are 'core', 'blocks', 'theme', and 'user'. 198 * @return bool True on success, false otherwise. 199 */ 200 protected static function has_same_registered_blocks( $origin ) { 201 // Bail out if the origin is invalid. 202 if ( ! isset( static::$blocks_cache[ $origin ] ) ) { 203 return false; 204 } 205 206 $registry = WP_Block_Type_Registry::get_instance(); 207 $blocks = $registry->get_all_registered(); 208 209 // Is there metadata for all currently registered blocks? 210 $block_diff = array_diff_key( $blocks, static::$blocks_cache[ $origin ] ); 211 if ( empty( $block_diff ) ) { 212 return true; 213 } 214 215 foreach ( $blocks as $block_name => $block_type ) { 216 static::$blocks_cache[ $origin ][ $block_name ] = true; 217 } 218 219 return false; 220 } 221 222 /** 223 * Returns the theme's data. 224 * 225 * Data from theme.json will be backfilled from existing 226 * theme supports, if any. Note that if the same data 227 * is present in theme.json and in theme supports, 228 * the theme.json takes precedence. 229 * 230 * @since 5.8.0 231 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. 232 * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. 233 * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. 234 * Added registration and merging of block style variations from partial theme.json files and the block styles registry. 235 * 236 * @param array $deprecated Deprecated. Not used. 237 * @param array $options { 238 * Options arguments. 239 * 240 * @type bool $with_supports Whether to include theme supports in the data. Default true. 241 * } 242 * @return WP_Theme_JSON Entity that holds theme data. 243 */ 244 public static function get_theme_data( $deprecated = array(), $options = array() ) { 245 if ( ! empty( $deprecated ) ) { 246 _deprecated_argument( __METHOD__, '5.9.0' ); 247 } 248 249 $options = wp_parse_args( $options, array( 'with_supports' => true ) ); 250 251 if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) { 252 $wp_theme = wp_get_theme(); 253 $theme_json_file = $wp_theme->get_file_path( 'theme.json' ); 254 if ( is_readable( $theme_json_file ) ) { 255 $theme_json_data = static::read_json_file( $theme_json_file ); 256 $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); 257 } else { 258 $theme_json_data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); 259 } 260 261 /* 262 * Register variations defined by theme partials (theme.json files in the styles directory). 263 * This is required so the variations pass sanitization of theme.json data. 264 */ 265 $variations = static::get_style_variations( 'block' ); 266 wp_register_block_style_variations_from_theme_json_partials( $variations ); 267 268 /* 269 * Source variations from the block registry and block style variation files. Then, merge them into the existing theme.json data. 270 * 271 * In case the same style properties are defined in several sources, this is how we should resolve the values, 272 * from higher to lower priority: 273 * 274 * - styles.blocks.blockType.variations from theme.json 275 * - styles.variations from theme.json 276 * - variations from block style variation files 277 * - variations from block styles registry 278 * 279 * See test_add_registered_block_styles_to_theme_data and test_unwraps_block_style_variations. 280 * 281 */ 282 $theme_json_data = static::inject_variations_from_block_style_variation_files( $theme_json_data, $variations ); 283 $theme_json_data = static::inject_variations_from_block_styles_registry( $theme_json_data ); 284 285 /** 286 * Filters the data provided by the theme for global styles and settings. 287 * 288 * @since 6.1.0 289 * 290 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. 291 */ 292 $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) ); 293 294 /* 295 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 296 * compatible class that is not a WP_Theme_JSON_Data object. 297 */ 298 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 299 static::$theme = $theme_json->get_theme_json(); 300 } else { 301 $config = $theme_json->get_data(); 302 static::$theme = new WP_Theme_JSON( $config ); 303 } 304 305 if ( $wp_theme->parent() ) { 306 // Get parent theme.json. 307 $parent_theme_json_file = $wp_theme->parent()->get_file_path( 'theme.json' ); 308 if ( $theme_json_file !== $parent_theme_json_file && is_readable( $parent_theme_json_file ) ) { 309 $parent_theme_json_data = static::read_json_file( $parent_theme_json_file ); 310 $parent_theme_json_data = static::translate( $parent_theme_json_data, $wp_theme->parent()->get( 'TextDomain' ) ); 311 $parent_theme = new WP_Theme_JSON( $parent_theme_json_data ); 312 313 /* 314 * Merge the child theme.json into the parent theme.json. 315 * The child theme takes precedence over the parent. 316 */ 317 $parent_theme->merge( static::$theme ); 318 static::$theme = $parent_theme; 319 } 320 } 321 } 322 323 if ( ! $options['with_supports'] ) { 324 return static::$theme; 325 } 326 327 /* 328 * We want the presets and settings declared in theme.json 329 * to override the ones declared via theme supports. 330 * So we take theme supports, transform it to theme.json shape 331 * and merge the static::$theme upon that. 332 */ 333 $theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_classic_theme_supports_block_editor_settings() ); 334 if ( ! wp_theme_has_theme_json() ) { 335 /* 336 * Unlike block themes, classic themes without a theme.json disable 337 * default presets when custom preset theme support is added. This 338 * behavior can be overridden by using the corresponding default 339 * preset theme support. 340 */ 341 $theme_support_data['settings']['color']['defaultPalette'] = 342 ! isset( $theme_support_data['settings']['color']['palette'] ) || 343 current_theme_supports( 'default-color-palette' ); 344 $theme_support_data['settings']['color']['defaultGradients'] = 345 ! isset( $theme_support_data['settings']['color']['gradients'] ) || 346 current_theme_supports( 'default-gradient-presets' ); 347 $theme_support_data['settings']['typography']['defaultFontSizes'] = 348 ! isset( $theme_support_data['settings']['typography']['fontSizes'] ) || 349 current_theme_supports( 'default-font-sizes' ); 350 $theme_support_data['settings']['spacing']['defaultSpacingSizes'] = 351 ! isset( $theme_support_data['settings']['spacing']['spacingSizes'] ) || 352 current_theme_supports( 'default-spacing-sizes' ); 353 354 /* 355 * Shadow presets are explicitly disabled for classic themes until a 356 * decision is made for whether the default presets should match the 357 * other presets or if they should be disabled by default in classic 358 * themes. See https://github.com/WordPress/gutenberg/issues/59989. 359 */ 360 $theme_support_data['settings']['shadow']['defaultPresets'] = false; 361 362 // Allow themes to enable link color setting via theme_support. 363 if ( current_theme_supports( 'link-color' ) ) { 364 $theme_support_data['settings']['color']['link'] = true; 365 } 366 367 // Allow themes to enable all border settings via theme_support. 368 if ( current_theme_supports( 'border' ) ) { 369 $theme_support_data['settings']['border']['color'] = true; 370 $theme_support_data['settings']['border']['radius'] = true; 371 $theme_support_data['settings']['border']['style'] = true; 372 $theme_support_data['settings']['border']['width'] = true; 373 } 374 375 // Allow themes to enable appearance tools via theme_support. 376 if ( current_theme_supports( 'appearance-tools' ) ) { 377 $theme_support_data['settings']['appearanceTools'] = true; 378 } 379 } 380 $with_theme_supports = new WP_Theme_JSON( $theme_support_data ); 381 $with_theme_supports->merge( static::$theme ); 382 return $with_theme_supports; 383 } 384 385 /** 386 * Gets the styles for blocks from the block.json file. 387 * 388 * @since 6.1.0 389 * 390 * @return WP_Theme_JSON 391 */ 392 public static function get_block_data() { 393 $registry = WP_Block_Type_Registry::get_instance(); 394 $blocks = $registry->get_all_registered(); 395 396 if ( null !== static::$blocks && static::has_same_registered_blocks( 'blocks' ) ) { 397 return static::$blocks; 398 } 399 400 $config = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); 401 foreach ( $blocks as $block_name => $block_type ) { 402 if ( isset( $block_type->supports['__experimentalStyle'] ) ) { 403 $config['styles']['blocks'][ $block_name ] = static::remove_json_comments( $block_type->supports['__experimentalStyle'] ); 404 } 405 406 if ( 407 isset( $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ) && 408 ! isset( $config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] ) 409 ) { 410 /* 411 * Ensure an empty placeholder value exists for the block, if it provides a default blockGap value. 412 * The real blockGap value to be used will be determined when the styles are rendered for output. 413 */ 414 $config['styles']['blocks'][ $block_name ]['spacing']['blockGap'] = null; 415 } 416 } 417 418 /** 419 * Filters the data provided by the blocks for global styles & settings. 420 * 421 * @since 6.1.0 422 * 423 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. 424 */ 425 $theme_json = apply_filters( 'wp_theme_json_data_blocks', new WP_Theme_JSON_Data( $config, 'blocks' ) ); 426 427 /* 428 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 429 * compatible class that is not a WP_Theme_JSON_Data object. 430 */ 431 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 432 static::$blocks = $theme_json->get_theme_json(); 433 } else { 434 $config = $theme_json->get_data(); 435 static::$blocks = new WP_Theme_JSON( $config, 'blocks' ); 436 } 437 438 return static::$blocks; 439 } 440 441 /** 442 * When given an array, this will remove any keys with the name `//`. 443 * 444 * @since 6.1.0 445 * 446 * @param array $input_array The array to filter. 447 * @return array The filtered array. 448 */ 449 private static function remove_json_comments( $input_array ) { 450 unset( $input_array['//'] ); 451 foreach ( $input_array as $k => $v ) { 452 if ( is_array( $v ) ) { 453 $input_array[ $k ] = static::remove_json_comments( $v ); 454 } 455 } 456 457 return $input_array; 458 } 459 460 /** 461 * Returns the custom post type that contains the user's origin config 462 * for the active theme or an empty array if none are found. 463 * 464 * This can also create and return a new draft custom post type. 465 * 466 * @since 5.9.0 467 * 468 * @param WP_Theme $theme The theme object. If empty, it 469 * defaults to the active theme. 470 * @param bool $create_post Optional. Whether a new custom post 471 * type should be created if none are 472 * found. Default false. 473 * @param array $post_status_filter Optional. Filter custom post type by 474 * post status. Default `array( 'publish' )`, 475 * so it only fetches published posts. 476 * @return array Custom Post Type for the user's origin config. 477 */ 478 public static function get_user_data_from_wp_global_styles( $theme, $create_post = false, $post_status_filter = array( 'publish' ) ) { 479 if ( ! $theme instanceof WP_Theme ) { 480 $theme = wp_get_theme(); 481 } 482 483 $user_cpt = array(); 484 $post_type_filter = 'wp_global_styles'; 485 $stylesheet = $theme->get_stylesheet(); 486 $args = array( 487 'posts_per_page' => 1, 488 'orderby' => 'date', 489 'order' => 'desc', 490 'post_type' => $post_type_filter, 491 'post_status' => $post_status_filter, 492 'ignore_sticky_posts' => true, 493 'no_found_rows' => true, 494 'update_post_meta_cache' => false, 495 'update_post_term_cache' => false, 496 'tax_query' => array( 497 array( 498 'taxonomy' => 'wp_theme', 499 'field' => 'name', 500 'terms' => $stylesheet, 501 ), 502 ), 503 ); 504 505 $global_style_query = new WP_Query(); 506 $recent_posts = $global_style_query->query( $args ); 507 if ( count( $recent_posts ) === 1 && $recent_posts[0] instanceof WP_Post ) { 508 $user_cpt = get_object_vars( $recent_posts[0] ); 509 } elseif ( $create_post ) { 510 $cpt_post_id = wp_insert_post( 511 array( 512 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', 513 'post_status' => 'publish', 514 'post_title' => 'Custom Styles', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518. 515 'post_type' => $post_type_filter, 516 'post_name' => sprintf( 'wp-global-styles-%s', urlencode( $stylesheet ) ), 517 'tax_input' => array( 518 'wp_theme' => array( $stylesheet ), 519 ), 520 ), 521 true 522 ); 523 if ( ! is_wp_error( $cpt_post_id ) ) { 524 $post = get_post( $cpt_post_id ); 525 if ( $post instanceof WP_Post ) { 526 $user_cpt = get_object_vars( $post ); 527 } 528 } 529 } 530 531 return $user_cpt; 532 } 533 534 /** 535 * Returns the user's origin config. 536 * 537 * @since 5.9.0 538 * @since 6.6.0 The 'isGlobalStylesUserThemeJSON' flag is left on the user data. 539 * Register the block style variations coming from the user data. 540 * 541 * @return WP_Theme_JSON Entity that holds styles for user data. 542 */ 543 public static function get_user_data() { 544 if ( null !== static::$user && static::has_same_registered_blocks( 'user' ) ) { 545 return static::$user; 546 } 547 548 $config = array(); 549 $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() ); 550 551 if ( array_key_exists( 'post_content', $user_cpt ) ) { 552 $decoded_data = json_decode( $user_cpt['post_content'], true ); 553 554 $json_decoding_error = json_last_error(); 555 if ( JSON_ERROR_NONE !== $json_decoding_error ) { 556 wp_trigger_error( __METHOD__, 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); 557 /** 558 * Filters the data provided by the user for global styles & settings. 559 * 560 * @since 6.1.0 561 * 562 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. 563 */ 564 $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); 565 566 /* 567 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 568 * compatible class that is not a WP_Theme_JSON_Data object. 569 */ 570 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 571 return $theme_json->get_theme_json(); 572 } else { 573 $config = $theme_json->get_data(); 574 return new WP_Theme_JSON( $config, 'custom' ); 575 } 576 } 577 578 /* 579 * Very important to verify that the flag isGlobalStylesUserThemeJSON is true. 580 * If it's not true then the content was not escaped and is not safe. 581 */ 582 if ( 583 is_array( $decoded_data ) && 584 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && 585 $decoded_data['isGlobalStylesUserThemeJSON'] 586 ) { 587 unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); 588 $config = $decoded_data; 589 } 590 } 591 592 /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ 593 $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); 594 595 /* 596 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 597 * compatible class that is not a WP_Theme_JSON_Data object. 598 */ 599 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 600 static::$user = $theme_json->get_theme_json(); 601 } else { 602 $config = $theme_json->get_data(); 603 static::$user = new WP_Theme_JSON( $config, 'custom' ); 604 } 605 606 return static::$user; 607 } 608 609 /** 610 * Returns the data merged from multiple origins. 611 * 612 * There are four sources of data (origins) for a site: 613 * 614 * - default => WordPress 615 * - blocks => each one of the blocks provides data for itself 616 * - theme => the active theme 617 * - custom => data provided by the user 618 * 619 * The custom's has higher priority than the theme's, the theme's higher than blocks', 620 * and block's higher than default's. 621 * 622 * Unlike the getters 623 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data}, 624 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data}, 625 * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data}, 626 * this method returns data after it has been merged with the previous origins. 627 * This means that if the same piece of data is declared in different origins 628 * (default, blocks, theme, custom), the last origin overrides the previous. 629 * 630 * For example, if the user has set a background color 631 * for the paragraph block, and the theme has done it as well, 632 * the user preference wins. 633 * 634 * @since 5.8.0 635 * @since 5.9.0 Added user data, removed the `$settings` parameter, 636 * added the `$origin` parameter. 637 * @since 6.1.0 Added block data and generation of spacingSizes array. 638 * @since 6.2.0 Changed ' $origin' parameter values to 'default', 'blocks', 'theme' or 'custom'. 639 * 640 * @param string $origin Optional. To what level should we merge data: 'default', 'blocks', 'theme' or 'custom'. 641 * 'custom' is used as default value as well as fallback value if the origin is unknown. 642 * @return WP_Theme_JSON 643 */ 644 public static function get_merged_data( $origin = 'custom' ) { 645 if ( is_array( $origin ) ) { 646 _deprecated_argument( __FUNCTION__, '5.9.0' ); 647 } 648 649 $result = new WP_Theme_JSON(); 650 $result->merge( static::get_core_data() ); 651 if ( 'default' === $origin ) { 652 return $result; 653 } 654 655 $result->merge( static::get_block_data() ); 656 if ( 'blocks' === $origin ) { 657 return $result; 658 } 659 660 $result->merge( static::get_theme_data() ); 661 if ( 'theme' === $origin ) { 662 return $result; 663 } 664 665 $result->merge( static::get_user_data() ); 666 667 return $result; 668 } 669 670 /** 671 * Returns the ID of the custom post type 672 * that stores user data. 673 * 674 * @since 5.9.0 675 * 676 * @return int|null ID for a post of type `wp_global_styles`, or null if not available. 677 */ 678 public static function get_user_global_styles_post_id() { 679 if ( null !== static::$user_custom_post_type_id ) { 680 return static::$user_custom_post_type_id; 681 } 682 683 $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme(), true ); 684 685 if ( array_key_exists( 'ID', $user_cpt ) ) { 686 static::$user_custom_post_type_id = $user_cpt['ID']; 687 } 688 689 return static::$user_custom_post_type_id; 690 } 691 692 /** 693 * Determines whether the active theme has a theme.json file. 694 * 695 * @since 5.8.0 696 * @since 5.9.0 Added a check in the parent theme. 697 * @deprecated 6.2.0 Use wp_theme_has_theme_json() instead. 698 * 699 * @return bool Whether the active theme has a theme.json file. 700 */ 701 public static function theme_has_support() { 702 _deprecated_function( __METHOD__, '6.2.0', 'wp_theme_has_theme_json()' ); 703 704 return wp_theme_has_theme_json(); 705 } 706 707 /** 708 * Builds the path to the given file and checks that it is readable. 709 * 710 * If it isn't, returns an empty string, otherwise returns the whole file path. 711 * 712 * @since 5.8.0 713 * @since 5.9.0 Adapted to work with child themes, added the `$template` argument. 714 * 715 * @param string $file_name Name of the file. 716 * @param bool $template Optional. Use template theme directory. Default false. 717 * @return string The whole file path or empty if the file doesn't exist. 718 */ 719 protected static function get_file_path_from_theme( $file_name, $template = false ) { 720 $path = $template ? get_template_directory() : get_stylesheet_directory(); 721 $candidate = $path . '/' . $file_name; 722 723 return is_readable( $candidate ) ? $candidate : ''; 724 } 725 726 /** 727 * Cleans the cached data so it can be recalculated. 728 * 729 * @since 5.8.0 730 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`, 731 * and `$i18n_schema` variables to reset. 732 * @since 6.1.0 Added the `$blocks` and `$blocks_cache` variables 733 * to reset. 734 */ 735 public static function clean_cached_data() { 736 static::$core = null; 737 static::$blocks = null; 738 static::$blocks_cache = array( 739 'core' => array(), 740 'blocks' => array(), 741 'theme' => array(), 742 'user' => array(), 743 ); 744 static::$theme = null; 745 static::$user = null; 746 static::$user_custom_post_type_id = null; 747 static::$i18n_schema = null; 748 } 749 750 /** 751 * Returns an array of all nested JSON files within a given directory. 752 * 753 * @since 6.2.0 754 * 755 * @param string $dir The directory to recursively iterate and list files of. 756 * @return array The merged array. 757 */ 758 private static function recursively_iterate_json( $dir ) { 759 $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ) ); 760 $nested_json_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); 761 return $nested_json_files; 762 } 763 764 /** 765 * Determines if a supplied style variation matches the provided scope. 766 * 767 * For backwards compatibility, if a variation does not define any scope 768 * related property, e.g. `blockTypes`, it is assumed to be a theme style 769 * variation. 770 * 771 * @since 6.6.0 772 * 773 * @param array $variation Theme.json shaped style variation object. 774 * @param string $scope Scope to check e.g. theme, block etc. 775 * @return bool Whether the supplied style variation matches the provided scope. 776 */ 777 private static function style_variation_has_scope( $variation, $scope ) { 778 if ( 'block' === $scope ) { 779 return isset( $variation['blockTypes'] ); 780 } 781 782 if ( 'theme' === $scope ) { 783 return ! isset( $variation['blockTypes'] ); 784 } 785 786 return false; 787 } 788 789 /** 790 * Returns the style variations defined by the theme. 791 * 792 * @since 6.0.0 793 * @since 6.2.0 Returns parent theme variations if theme is a child. 794 * @since 6.6.0 Added configurable scope parameter to allow filtering 795 * theme.json partial files by the scope to which they 796 * can be applied e.g. theme vs block etc. 797 * Added basic caching for read theme.json partial files. 798 * 799 * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. 800 * @return array The style variations defined by the theme. 801 */ 802 public static function get_style_variations( $scope = 'theme' ) { 803 $variation_files = array(); 804 $variations = array(); 805 $base_directory = get_stylesheet_directory() . '/styles'; 806 $template_directory = get_template_directory() . '/styles'; 807 if ( is_dir( $base_directory ) ) { 808 $variation_files = static::recursively_iterate_json( $base_directory ); 809 } 810 if ( is_dir( $template_directory ) && $template_directory !== $base_directory ) { 811 $variation_files_parent = static::recursively_iterate_json( $template_directory ); 812 // If the child and parent variation file basename are the same, only include the child theme's. 813 foreach ( $variation_files_parent as $parent_path => $parent ) { 814 foreach ( $variation_files as $child_path => $child ) { 815 if ( basename( $parent_path ) === basename( $child_path ) ) { 816 unset( $variation_files_parent[ $parent_path ] ); 817 } 818 } 819 } 820 $variation_files = array_merge( $variation_files, $variation_files_parent ); 821 } 822 ksort( $variation_files ); 823 foreach ( $variation_files as $path => $file ) { 824 $decoded_file = self::read_json_file( $path ); 825 if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { 826 $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); 827 $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); 828 if ( empty( $variation['title'] ) ) { 829 $variation['title'] = basename( $path, '.json' ); 830 } 831 $variations[] = $variation; 832 } 833 } 834 return $variations; 835 } 836 837 /** 838 * Resolves relative paths in theme.json styles to theme absolute paths 839 * and returns them in an array that can be embedded 840 * as the value of `_link` object in REST API responses. 841 * 842 * @since 6.6.0 843 * @since 6.7.0 Resolve relative paths in block styles. 844 * 845 * @param WP_Theme_JSON $theme_json A theme json instance. 846 * @return array An array of resolved paths. 847 */ 848 public static function get_resolved_theme_uris( $theme_json ) { 849 $resolved_theme_uris = array(); 850 851 if ( ! $theme_json instanceof WP_Theme_JSON ) { 852 return $resolved_theme_uris; 853 } 854 855 $theme_json_data = $theme_json->get_raw_data(); 856 /* 857 * The same file convention when registering web fonts. 858 * See: WP_Font_Face_Resolver::to_theme_file_uri. 859 */ 860 $placeholder = 'file:./'; 861 862 // Top level styles. 863 $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; 864 if ( 865 isset( $background_image_url ) && 866 is_string( $background_image_url ) && 867 // Skip if the src doesn't start with the placeholder, as there's nothing to replace. 868 str_starts_with( $background_image_url, $placeholder ) 869 ) { 870 $file_type = wp_check_filetype( $background_image_url ); 871 $src_url = str_replace( $placeholder, '', $background_image_url ); 872 $resolved_theme_uri = array( 873 'name' => $background_image_url, 874 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 875 'target' => 'styles.background.backgroundImage.url', 876 ); 877 if ( isset( $file_type['type'] ) ) { 878 $resolved_theme_uri['type'] = $file_type['type']; 879 } 880 $resolved_theme_uris[] = $resolved_theme_uri; 881 } 882 883 // Block styles. 884 if ( ! empty( $theme_json_data['styles']['blocks'] ) ) { 885 foreach ( $theme_json_data['styles']['blocks'] as $block_name => $block_styles ) { 886 if ( ! isset( $block_styles['background']['backgroundImage']['url'] ) ) { 887 continue; 888 } 889 $background_image_url = $block_styles['background']['backgroundImage']['url']; 890 if ( 891 is_string( $background_image_url ) && 892 // Skip if the src doesn't start with the placeholder, as there's nothing to replace. 893 str_starts_with( $background_image_url, $placeholder ) 894 ) { 895 $file_type = wp_check_filetype( $background_image_url ); 896 $src_url = str_replace( $placeholder, '', $background_image_url ); 897 $resolved_theme_uri = array( 898 'name' => $background_image_url, 899 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 900 'target' => "styles.blocks.{$block_name}.background.backgroundImage.url", 901 ); 902 if ( isset( $file_type['type'] ) ) { 903 $resolved_theme_uri['type'] = $file_type['type']; 904 } 905 $resolved_theme_uris[] = $resolved_theme_uri; 906 } 907 } 908 } 909 910 return $resolved_theme_uris; 911 } 912 913 /** 914 * Resolves relative paths in theme.json styles to theme absolute paths 915 * and merges them with incoming theme JSON. 916 * 917 * @since 6.6.0 918 * 919 * @param WP_Theme_JSON $theme_json A theme json instance. 920 * @return WP_Theme_JSON Theme merged with resolved paths, if any found. 921 */ 922 public static function resolve_theme_file_uris( $theme_json ) { 923 $resolved_urls = static::get_resolved_theme_uris( $theme_json ); 924 if ( empty( $resolved_urls ) ) { 925 return $theme_json; 926 } 927 928 $resolved_theme_json_data = $theme_json->get_raw_data(); 929 930 foreach ( $resolved_urls as $resolved_url ) { 931 $path = explode( '.', $resolved_url['target'] ); 932 _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); 933 } 934 935 return new WP_Theme_JSON( $resolved_theme_json_data ); 936 } 937 938 /** 939 * Adds variations sourced from block style variations files to the supplied theme.json data. 940 * 941 * @since 6.6.0 942 * 943 * @param array $data Array following the theme.json specification. 944 * @param array $variations Shared block style variations. 945 * @return array Theme json data including shared block style variation definitions. 946 */ 947 private static function inject_variations_from_block_style_variation_files( $data, $variations ) { 948 if ( empty( $variations ) ) { 949 return $data; 950 } 951 952 foreach ( $variations as $variation ) { 953 if ( empty( $variation['styles'] ) || empty( $variation['blockTypes'] ) ) { 954 continue; 955 } 956 957 $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); 958 959 foreach ( $variation['blockTypes'] as $block_type ) { 960 // First, override partial styles with any top-level styles. 961 $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); 962 if ( ! empty( $top_level_data ) ) { 963 $variation['styles'] = array_replace_recursive( $variation['styles'], $top_level_data ); 964 } 965 966 // Then, override styles so far with any block-level styles. 967 $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); 968 if ( ! empty( $block_level_data ) ) { 969 $variation['styles'] = array_replace_recursive( $variation['styles'], $block_level_data ); 970 } 971 972 $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); 973 _wp_array_set( $data, $path, $variation['styles'] ); 974 } 975 } 976 977 return $data; 978 } 979 980 /** 981 * Adds variations sourced from the block styles registry to the supplied theme.json data. 982 * 983 * @since 6.6.0 984 * 985 * @param array $data Array following the theme.json specification. 986 * @return array Theme json data including shared block style variation definitions. 987 */ 988 private static function inject_variations_from_block_styles_registry( $data ) { 989 $registry = WP_Block_Styles_Registry::get_instance(); 990 $styles = $registry->get_all_registered(); 991 992 foreach ( $styles as $block_type => $variations ) { 993 foreach ( $variations as $variation_name => $variation ) { 994 if ( empty( $variation['style_data'] ) ) { 995 continue; 996 } 997 998 // First, override registry styles with any top-level styles. 999 $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); 1000 if ( ! empty( $top_level_data ) ) { 1001 $variation['style_data'] = array_replace_recursive( $variation['style_data'], $top_level_data ); 1002 } 1003 1004 // Then, override styles so far with any block-level styles. 1005 $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); 1006 if ( ! empty( $block_level_data ) ) { 1007 $variation['style_data'] = array_replace_recursive( $variation['style_data'], $block_level_data ); 1008 } 1009 1010 $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); 1011 _wp_array_set( $data, $path, $variation['style_data'] ); 1012 } 1013 } 1014 1015 return $data; 1016 } 1017 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue May 5 08:20:14 2026 | Cross-referenced by PHPXref |