[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-wp-theme-json-resolver.php (source)

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


Generated: Thu Apr 25 01:00:03 2024 Cross-referenced by PHPXref 0.7.1