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