[ 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 /* 484 * Bail early if the theme does not support a theme.json. 485 * 486 * Since wp_theme_has_theme_json() only supports the active 487 * theme, the extra condition for whether $theme is the active theme is 488 * present here. 489 */ 490 if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) { 491 return array(); 492 } 493 494 $user_cpt = array(); 495 $post_type_filter = 'wp_global_styles'; 496 $stylesheet = $theme->get_stylesheet(); 497 $args = array( 498 'posts_per_page' => 1, 499 'orderby' => 'date', 500 'order' => 'desc', 501 'post_type' => $post_type_filter, 502 'post_status' => $post_status_filter, 503 'ignore_sticky_posts' => true, 504 'no_found_rows' => true, 505 'update_post_meta_cache' => false, 506 'update_post_term_cache' => false, 507 'tax_query' => array( 508 array( 509 'taxonomy' => 'wp_theme', 510 'field' => 'name', 511 'terms' => $stylesheet, 512 ), 513 ), 514 ); 515 516 $global_style_query = new WP_Query(); 517 $recent_posts = $global_style_query->query( $args ); 518 if ( count( $recent_posts ) === 1 ) { 519 $user_cpt = get_object_vars( $recent_posts[0] ); 520 } elseif ( $create_post ) { 521 $cpt_post_id = wp_insert_post( 522 array( 523 'post_content' => '{"version": ' . WP_Theme_JSON::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }', 524 'post_status' => 'publish', 525 'post_title' => 'Custom Styles', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518. 526 'post_type' => $post_type_filter, 527 'post_name' => sprintf( 'wp-global-styles-%s', urlencode( $stylesheet ) ), 528 'tax_input' => array( 529 'wp_theme' => array( $stylesheet ), 530 ), 531 ), 532 true 533 ); 534 if ( ! is_wp_error( $cpt_post_id ) ) { 535 $user_cpt = get_object_vars( get_post( $cpt_post_id ) ); 536 } 537 } 538 539 return $user_cpt; 540 } 541 542 /** 543 * Returns the user's origin config. 544 * 545 * @since 5.9.0 546 * @since 6.6.0 The 'isGlobalStylesUserThemeJSON' flag is left on the user data. 547 * Register the block style variations coming from the user data. 548 * 549 * @return WP_Theme_JSON Entity that holds styles for user data. 550 */ 551 public static function get_user_data() { 552 if ( null !== static::$user && static::has_same_registered_blocks( 'user' ) ) { 553 return static::$user; 554 } 555 556 $config = array(); 557 $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme() ); 558 559 if ( array_key_exists( 'post_content', $user_cpt ) ) { 560 $decoded_data = json_decode( $user_cpt['post_content'], true ); 561 562 $json_decoding_error = json_last_error(); 563 if ( JSON_ERROR_NONE !== $json_decoding_error ) { 564 wp_trigger_error( __METHOD__, 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); 565 /** 566 * Filters the data provided by the user for global styles & settings. 567 * 568 * @since 6.1.0 569 * 570 * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. 571 */ 572 $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); 573 574 /* 575 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 576 * compatible class that is not a WP_Theme_JSON_Data object. 577 */ 578 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 579 return $theme_json->get_theme_json(); 580 } else { 581 $config = $theme_json->get_data(); 582 return new WP_Theme_JSON( $config, 'custom' ); 583 } 584 } 585 586 /* 587 * Very important to verify that the flag isGlobalStylesUserThemeJSON is true. 588 * If it's not true then the content was not escaped and is not safe. 589 */ 590 if ( 591 is_array( $decoded_data ) && 592 isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) && 593 $decoded_data['isGlobalStylesUserThemeJSON'] 594 ) { 595 unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); 596 $config = $decoded_data; 597 } 598 } 599 600 /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ 601 $theme_json = apply_filters( 'wp_theme_json_data_user', new WP_Theme_JSON_Data( $config, 'custom' ) ); 602 603 /* 604 * Backward compatibility for extenders returning a WP_Theme_JSON_Data 605 * compatible class that is not a WP_Theme_JSON_Data object. 606 */ 607 if ( $theme_json instanceof WP_Theme_JSON_Data ) { 608 static::$user = $theme_json->get_theme_json(); 609 } else { 610 $config = $theme_json->get_data(); 611 static::$user = new WP_Theme_JSON( $config, 'custom' ); 612 } 613 614 return static::$user; 615 } 616 617 /** 618 * Returns the data merged from multiple origins. 619 * 620 * There are four sources of data (origins) for a site: 621 * 622 * - default => WordPress 623 * - blocks => each one of the blocks provides data for itself 624 * - theme => the active theme 625 * - custom => data provided by the user 626 * 627 * The custom's has higher priority than the theme's, the theme's higher than blocks', 628 * and block's higher than default's. 629 * 630 * Unlike the getters 631 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_core_data/ get_core_data}, 632 * {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_theme_data/ get_theme_data}, 633 * and {@link https://developer.wordpress.org/reference/classes/wp_theme_json_resolver/get_user_data/ get_user_data}, 634 * this method returns data after it has been merged with the previous origins. 635 * This means that if the same piece of data is declared in different origins 636 * (default, blocks, theme, custom), the last origin overrides the previous. 637 * 638 * For example, if the user has set a background color 639 * for the paragraph block, and the theme has done it as well, 640 * the user preference wins. 641 * 642 * @since 5.8.0 643 * @since 5.9.0 Added user data, removed the `$settings` parameter, 644 * added the `$origin` parameter. 645 * @since 6.1.0 Added block data and generation of spacingSizes array. 646 * @since 6.2.0 Changed ' $origin' parameter values to 'default', 'blocks', 'theme' or 'custom'. 647 * 648 * @param string $origin Optional. To what level should we merge data: 'default', 'blocks', 'theme' or 'custom'. 649 * 'custom' is used as default value as well as fallback value if the origin is unknown. 650 * @return WP_Theme_JSON 651 */ 652 public static function get_merged_data( $origin = 'custom' ) { 653 if ( is_array( $origin ) ) { 654 _deprecated_argument( __FUNCTION__, '5.9.0' ); 655 } 656 657 $result = new WP_Theme_JSON(); 658 $result->merge( static::get_core_data() ); 659 if ( 'default' === $origin ) { 660 return $result; 661 } 662 663 $result->merge( static::get_block_data() ); 664 if ( 'blocks' === $origin ) { 665 return $result; 666 } 667 668 $result->merge( static::get_theme_data() ); 669 if ( 'theme' === $origin ) { 670 return $result; 671 } 672 673 $result->merge( static::get_user_data() ); 674 675 return $result; 676 } 677 678 /** 679 * Returns the ID of the custom post type 680 * that stores user data. 681 * 682 * @since 5.9.0 683 * 684 * @return integer|null 685 */ 686 public static function get_user_global_styles_post_id() { 687 if ( null !== static::$user_custom_post_type_id ) { 688 return static::$user_custom_post_type_id; 689 } 690 691 $user_cpt = static::get_user_data_from_wp_global_styles( wp_get_theme(), true ); 692 693 if ( array_key_exists( 'ID', $user_cpt ) ) { 694 static::$user_custom_post_type_id = $user_cpt['ID']; 695 } 696 697 return static::$user_custom_post_type_id; 698 } 699 700 /** 701 * Determines whether the active theme has a theme.json file. 702 * 703 * @since 5.8.0 704 * @since 5.9.0 Added a check in the parent theme. 705 * @deprecated 6.2.0 Use wp_theme_has_theme_json() instead. 706 * 707 * @return bool 708 */ 709 public static function theme_has_support() { 710 _deprecated_function( __METHOD__, '6.2.0', 'wp_theme_has_theme_json()' ); 711 712 return wp_theme_has_theme_json(); 713 } 714 715 /** 716 * Builds the path to the given file and checks that it is readable. 717 * 718 * If it isn't, returns an empty string, otherwise returns the whole file path. 719 * 720 * @since 5.8.0 721 * @since 5.9.0 Adapted to work with child themes, added the `$template` argument. 722 * 723 * @param string $file_name Name of the file. 724 * @param bool $template Optional. Use template theme directory. Default false. 725 * @return string The whole file path or empty if the file doesn't exist. 726 */ 727 protected static function get_file_path_from_theme( $file_name, $template = false ) { 728 $path = $template ? get_template_directory() : get_stylesheet_directory(); 729 $candidate = $path . '/' . $file_name; 730 731 return is_readable( $candidate ) ? $candidate : ''; 732 } 733 734 /** 735 * Cleans the cached data so it can be recalculated. 736 * 737 * @since 5.8.0 738 * @since 5.9.0 Added the `$user`, `$user_custom_post_type_id`, 739 * and `$i18n_schema` variables to reset. 740 * @since 6.1.0 Added the `$blocks` and `$blocks_cache` variables 741 * to reset. 742 */ 743 public static function clean_cached_data() { 744 static::$core = null; 745 static::$blocks = null; 746 static::$blocks_cache = array( 747 'core' => array(), 748 'blocks' => array(), 749 'theme' => array(), 750 'user' => array(), 751 ); 752 static::$theme = null; 753 static::$user = null; 754 static::$user_custom_post_type_id = null; 755 static::$i18n_schema = null; 756 } 757 758 /** 759 * Returns an array of all nested JSON files within a given directory. 760 * 761 * @since 6.2.0 762 * 763 * @param string $dir The directory to recursively iterate and list files of. 764 * @return array The merged array. 765 */ 766 private static function recursively_iterate_json( $dir ) { 767 $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ) ); 768 $nested_json_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); 769 return $nested_json_files; 770 } 771 772 /** 773 * Determines if a supplied style variation matches the provided scope. 774 * 775 * For backwards compatibility, if a variation does not define any scope 776 * related property, e.g. `blockTypes`, it is assumed to be a theme style 777 * variation. 778 * 779 * @since 6.6.0 780 * 781 * @param array $variation Theme.json shaped style variation object. 782 * @param string $scope Scope to check e.g. theme, block etc. 783 * @return boolean 784 */ 785 private static function style_variation_has_scope( $variation, $scope ) { 786 if ( 'block' === $scope ) { 787 return isset( $variation['blockTypes'] ); 788 } 789 790 if ( 'theme' === $scope ) { 791 return ! isset( $variation['blockTypes'] ); 792 } 793 794 return false; 795 } 796 797 /** 798 * Returns the style variations defined by the theme. 799 * 800 * @since 6.0.0 801 * @since 6.2.0 Returns parent theme variations if theme is a child. 802 * @since 6.6.0 Added configurable scope parameter to allow filtering 803 * theme.json partial files by the scope to which they 804 * can be applied e.g. theme vs block etc. 805 * Added basic caching for read theme.json partial files. 806 * 807 * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. 808 * @return array 809 */ 810 public static function get_style_variations( $scope = 'theme' ) { 811 $variation_files = array(); 812 $variations = array(); 813 $base_directory = get_stylesheet_directory() . '/styles'; 814 $template_directory = get_template_directory() . '/styles'; 815 if ( is_dir( $base_directory ) ) { 816 $variation_files = static::recursively_iterate_json( $base_directory ); 817 } 818 if ( is_dir( $template_directory ) && $template_directory !== $base_directory ) { 819 $variation_files_parent = static::recursively_iterate_json( $template_directory ); 820 // If the child and parent variation file basename are the same, only include the child theme's. 821 foreach ( $variation_files_parent as $parent_path => $parent ) { 822 foreach ( $variation_files as $child_path => $child ) { 823 if ( basename( $parent_path ) === basename( $child_path ) ) { 824 unset( $variation_files_parent[ $parent_path ] ); 825 } 826 } 827 } 828 $variation_files = array_merge( $variation_files, $variation_files_parent ); 829 } 830 ksort( $variation_files ); 831 foreach ( $variation_files as $path => $file ) { 832 $decoded_file = self::read_json_file( $path ); 833 if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { 834 $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); 835 $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); 836 if ( empty( $variation['title'] ) ) { 837 $variation['title'] = basename( $path, '.json' ); 838 } 839 $variations[] = $variation; 840 } 841 } 842 return $variations; 843 } 844 845 /** 846 * Resolves relative paths in theme.json styles to theme absolute paths 847 * and returns them in an array that can be embedded 848 * as the value of `_link` object in REST API responses. 849 * 850 * @since 6.6.0 851 * @since 6.7.0 Resolve relative paths in block styles. 852 * 853 * @param WP_Theme_JSON $theme_json A theme json instance. 854 * @return array An array of resolved paths. 855 */ 856 public static function get_resolved_theme_uris( $theme_json ) { 857 $resolved_theme_uris = array(); 858 859 if ( ! $theme_json instanceof WP_Theme_JSON ) { 860 return $resolved_theme_uris; 861 } 862 863 $theme_json_data = $theme_json->get_raw_data(); 864 /* 865 * The same file convention when registering web fonts. 866 * See: WP_Font_Face_Resolver::to_theme_file_uri. 867 */ 868 $placeholder = 'file:./'; 869 870 // Top level styles. 871 $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; 872 if ( 873 isset( $background_image_url ) && 874 is_string( $background_image_url ) && 875 // Skip if the src doesn't start with the placeholder, as there's nothing to replace. 876 str_starts_with( $background_image_url, $placeholder ) 877 ) { 878 $file_type = wp_check_filetype( $background_image_url ); 879 $src_url = str_replace( $placeholder, '', $background_image_url ); 880 $resolved_theme_uri = array( 881 'name' => $background_image_url, 882 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 883 'target' => 'styles.background.backgroundImage.url', 884 ); 885 if ( isset( $file_type['type'] ) ) { 886 $resolved_theme_uri['type'] = $file_type['type']; 887 } 888 $resolved_theme_uris[] = $resolved_theme_uri; 889 } 890 891 // Block styles. 892 if ( ! empty( $theme_json_data['styles']['blocks'] ) ) { 893 foreach ( $theme_json_data['styles']['blocks'] as $block_name => $block_styles ) { 894 if ( ! isset( $block_styles['background']['backgroundImage']['url'] ) ) { 895 continue; 896 } 897 $background_image_url = $block_styles['background']['backgroundImage']['url']; 898 if ( 899 is_string( $background_image_url ) && 900 // Skip if the src doesn't start with the placeholder, as there's nothing to replace. 901 str_starts_with( $background_image_url, $placeholder ) 902 ) { 903 $file_type = wp_check_filetype( $background_image_url ); 904 $src_url = str_replace( $placeholder, '', $background_image_url ); 905 $resolved_theme_uri = array( 906 'name' => $background_image_url, 907 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), 908 'target' => "styles.blocks.{$block_name}.background.backgroundImage.url", 909 ); 910 if ( isset( $file_type['type'] ) ) { 911 $resolved_theme_uri['type'] = $file_type['type']; 912 } 913 $resolved_theme_uris[] = $resolved_theme_uri; 914 } 915 } 916 } 917 918 return $resolved_theme_uris; 919 } 920 921 /** 922 * Resolves relative paths in theme.json styles to theme absolute paths 923 * and merges them with incoming theme JSON. 924 * 925 * @since 6.6.0 926 * 927 * @param WP_Theme_JSON $theme_json A theme json instance. 928 * @return WP_Theme_JSON Theme merged with resolved paths, if any found. 929 */ 930 public static function resolve_theme_file_uris( $theme_json ) { 931 $resolved_urls = static::get_resolved_theme_uris( $theme_json ); 932 if ( empty( $resolved_urls ) ) { 933 return $theme_json; 934 } 935 936 $resolved_theme_json_data = $theme_json->get_raw_data(); 937 938 foreach ( $resolved_urls as $resolved_url ) { 939 $path = explode( '.', $resolved_url['target'] ); 940 _wp_array_set( $resolved_theme_json_data, $path, $resolved_url['href'] ); 941 } 942 943 return new WP_Theme_JSON( $resolved_theme_json_data ); 944 } 945 946 /** 947 * Adds variations sourced from block style variations files to the supplied theme.json data. 948 * 949 * @since 6.6.0 950 * 951 * @param array $data Array following the theme.json specification. 952 * @param array $variations Shared block style variations. 953 * @return array Theme json data including shared block style variation definitions. 954 */ 955 private static function inject_variations_from_block_style_variation_files( $data, $variations ) { 956 if ( empty( $variations ) ) { 957 return $data; 958 } 959 960 foreach ( $variations as $variation ) { 961 if ( empty( $variation['styles'] ) || empty( $variation['blockTypes'] ) ) { 962 continue; 963 } 964 965 $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); 966 967 foreach ( $variation['blockTypes'] as $block_type ) { 968 // First, override partial styles with any top-level styles. 969 $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); 970 if ( ! empty( $top_level_data ) ) { 971 $variation['styles'] = array_replace_recursive( $variation['styles'], $top_level_data ); 972 } 973 974 // Then, override styles so far with any block-level styles. 975 $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); 976 if ( ! empty( $block_level_data ) ) { 977 $variation['styles'] = array_replace_recursive( $variation['styles'], $block_level_data ); 978 } 979 980 $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); 981 _wp_array_set( $data, $path, $variation['styles'] ); 982 } 983 } 984 985 return $data; 986 } 987 988 /** 989 * Adds variations sourced from the block styles registry to the supplied theme.json data. 990 * 991 * @since 6.6.0 992 * 993 * @param array $data Array following the theme.json specification. 994 * @return array Theme json data including shared block style variation definitions. 995 */ 996 private static function inject_variations_from_block_styles_registry( $data ) { 997 $registry = WP_Block_Styles_Registry::get_instance(); 998 $styles = $registry->get_all_registered(); 999 1000 foreach ( $styles as $block_type => $variations ) { 1001 foreach ( $variations as $variation_name => $variation ) { 1002 if ( empty( $variation['style_data'] ) ) { 1003 continue; 1004 } 1005 1006 // First, override registry styles with any top-level styles. 1007 $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); 1008 if ( ! empty( $top_level_data ) ) { 1009 $variation['style_data'] = array_replace_recursive( $variation['style_data'], $top_level_data ); 1010 } 1011 1012 // Then, override styles so far with any block-level styles. 1013 $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); 1014 if ( ! empty( $block_level_data ) ) { 1015 $variation['style_data'] = array_replace_recursive( $variation['style_data'], $block_level_data ); 1016 } 1017 1018 $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); 1019 _wp_array_set( $data, $path, $variation['style_data'] ); 1020 } 1021 } 1022 1023 return $data; 1024 } 1025 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |