[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> theme.php (source)

   1  <?php
   2  /**
   3   * Theme, template, and stylesheet functions.
   4   *
   5   * @package WordPress
   6   * @subpackage Theme
   7   */
   8  
   9  /**
  10   * Returns an array of WP_Theme objects based on the arguments.
  11   *
  12   * Despite advances over get_themes(), this function is quite expensive, and grows
  13   * linearly with additional themes. Stick to wp_get_theme() if possible.
  14   *
  15   * @since 3.4.0
  16   *
  17   * @global string[] $wp_theme_directories
  18   *
  19   * @param array $args {
  20   *     Optional. The search arguments.
  21   *
  22   *     @type mixed $errors  True to return themes with errors, false to return
  23   *                          themes without errors, null to return all themes.
  24   *                          Default false.
  25   *     @type mixed $allowed (Multisite) True to return only allowed themes for a site.
  26   *                          False to return only disallowed themes for a site.
  27   *                          'site' to return only site-allowed themes.
  28   *                          'network' to return only network-allowed themes.
  29   *                          Null to return all themes. Default null.
  30   *     @type int   $blog_id (Multisite) The blog ID used to calculate which themes
  31   *                          are allowed. Default 0, synonymous for the current blog.
  32   * }
  33   * @return WP_Theme[] Array of WP_Theme objects.
  34   */
  35  function wp_get_themes( $args = array() ) {
  36      global $wp_theme_directories;
  37  
  38      $defaults = array(
  39          'errors'  => false,
  40          'allowed' => null,
  41          'blog_id' => 0,
  42      );
  43      $args     = wp_parse_args( $args, $defaults );
  44  
  45      $theme_directories = search_theme_directories();
  46  
  47      if ( is_array( $wp_theme_directories ) && count( $wp_theme_directories ) > 1 ) {
  48          /*
  49           * Make sure the active theme wins out, in case search_theme_directories() picks the wrong
  50           * one in the case of a conflict. (Normally, last registered theme root wins.)
  51           */
  52          $current_theme = get_stylesheet();
  53          if ( isset( $theme_directories[ $current_theme ] ) ) {
  54              $root_of_current_theme = get_raw_theme_root( $current_theme );
  55              if ( ! in_array( $root_of_current_theme, $wp_theme_directories, true ) ) {
  56                  $root_of_current_theme = WP_CONTENT_DIR . $root_of_current_theme;
  57              }
  58              $theme_directories[ $current_theme ]['theme_root'] = $root_of_current_theme;
  59          }
  60      }
  61  
  62      if ( empty( $theme_directories ) ) {
  63          return array();
  64      }
  65  
  66      if ( is_multisite() && null !== $args['allowed'] ) {
  67          $allowed = $args['allowed'];
  68          if ( 'network' === $allowed ) {
  69              $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_network() );
  70          } elseif ( 'site' === $allowed ) {
  71              $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_site( $args['blog_id'] ) );
  72          } elseif ( $allowed ) {
  73              $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
  74          } else {
  75              $theme_directories = array_diff_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
  76          }
  77      }
  78  
  79      $themes         = array();
  80      static $_themes = array();
  81  
  82      foreach ( $theme_directories as $theme => $theme_root ) {
  83          if ( isset( $_themes[ $theme_root['theme_root'] . '/' . $theme ] ) ) {
  84              $themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ];
  85          } else {
  86              $themes[ $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
  87  
  88              $_themes[ $theme_root['theme_root'] . '/' . $theme ] = $themes[ $theme ];
  89          }
  90      }
  91  
  92      if ( null !== $args['errors'] ) {
  93          foreach ( $themes as $theme => $wp_theme ) {
  94              if ( (bool) $wp_theme->errors() !== $args['errors'] ) {
  95                  unset( $themes[ $theme ] );
  96              }
  97          }
  98      }
  99  
 100      return $themes;
 101  }
 102  
 103  /**
 104   * Gets a WP_Theme object for a theme.
 105   *
 106   * @since 3.4.0
 107   *
 108   * @global string[] $wp_theme_directories
 109   *
 110   * @param string $stylesheet Optional. Directory name for the theme. Defaults to active theme.
 111   * @param string $theme_root Optional. Absolute path of the theme root to look in.
 112   *                           If not specified, get_raw_theme_root() is used to calculate
 113   *                           the theme root for the $stylesheet provided (or active theme).
 114   * @return WP_Theme Theme object. Be sure to check the object's exists() method
 115   *                  if you need to confirm the theme's existence.
 116   */
 117  function wp_get_theme( $stylesheet = '', $theme_root = '' ) {
 118      global $wp_theme_directories;
 119  
 120      if ( empty( $stylesheet ) ) {
 121          $stylesheet = get_stylesheet();
 122      }
 123  
 124      if ( empty( $theme_root ) ) {
 125          $theme_root = get_raw_theme_root( $stylesheet );
 126          if ( false === $theme_root ) {
 127              $theme_root = WP_CONTENT_DIR . '/themes';
 128          } elseif ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
 129              $theme_root = WP_CONTENT_DIR . $theme_root;
 130          }
 131      }
 132  
 133      return new WP_Theme( $stylesheet, $theme_root );
 134  }
 135  
 136  /**
 137   * Clears the cache held by get_theme_roots() and WP_Theme.
 138   *
 139   * @since 3.5.0
 140   * @param bool $clear_update_cache Whether to clear the theme updates cache.
 141   */
 142  function wp_clean_themes_cache( $clear_update_cache = true ) {
 143      if ( $clear_update_cache ) {
 144          delete_site_transient( 'update_themes' );
 145      }
 146      search_theme_directories( true );
 147      foreach ( wp_get_themes( array( 'errors' => null ) ) as $theme ) {
 148          $theme->cache_delete();
 149      }
 150  }
 151  
 152  /**
 153   * Whether a child theme is in use.
 154   *
 155   * @since 3.0.0
 156   * @since 6.5.0 Makes use of global template variables.
 157   *
 158   * @global string $wp_stylesheet_path Path to current theme's stylesheet directory.
 159   * @global string $wp_template_path   Path to current theme's template directory.
 160   *
 161   * @return bool True if a child theme is in use, false otherwise.
 162   */
 163  function is_child_theme() {
 164      global $wp_stylesheet_path, $wp_template_path;
 165  
 166      return $wp_stylesheet_path !== $wp_template_path;
 167  }
 168  
 169  /**
 170   * Retrieves name of the current stylesheet.
 171   *
 172   * The theme name that is currently set as the front end theme.
 173   *
 174   * For all intents and purposes, the template name and the stylesheet name
 175   * are going to be the same for most cases.
 176   *
 177   * @since 1.5.0
 178   *
 179   * @return string Stylesheet name.
 180   */
 181  function get_stylesheet() {
 182      /**
 183       * Filters the name of current stylesheet.
 184       *
 185       * @since 1.5.0
 186       *
 187       * @param string $stylesheet Name of the current stylesheet.
 188       */
 189      return apply_filters( 'stylesheet', get_option( 'stylesheet' ) );
 190  }
 191  
 192  /**
 193   * Retrieves stylesheet directory path for the active theme.
 194   *
 195   * @since 1.5.0
 196   * @since 6.4.0 Memoizes filter execution so that it only runs once for the current theme.
 197   * @since 6.4.2 Memoization removed.
 198   *
 199   * @return string Path to active theme's stylesheet directory.
 200   */
 201  function get_stylesheet_directory() {
 202      $stylesheet     = get_stylesheet();
 203      $theme_root     = get_theme_root( $stylesheet );
 204      $stylesheet_dir = "$theme_root/$stylesheet";
 205  
 206      /**
 207       * Filters the stylesheet directory path for the active theme.
 208       *
 209       * @since 1.5.0
 210       *
 211       * @param string $stylesheet_dir Absolute path to the active theme.
 212       * @param string $stylesheet     Directory name of the active theme.
 213       * @param string $theme_root     Absolute path to themes directory.
 214       */
 215      return apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
 216  }
 217  
 218  /**
 219   * Retrieves stylesheet directory URI for the active theme.
 220   *
 221   * @since 1.5.0
 222   *
 223   * @return string URI to active theme's stylesheet directory.
 224   */
 225  function get_stylesheet_directory_uri() {
 226      $stylesheet         = str_replace( '%2F', '/', rawurlencode( get_stylesheet() ) );
 227      $theme_root_uri     = get_theme_root_uri( $stylesheet );
 228      $stylesheet_dir_uri = "$theme_root_uri/$stylesheet";
 229  
 230      /**
 231       * Filters the stylesheet directory URI.
 232       *
 233       * @since 1.5.0
 234       *
 235       * @param string $stylesheet_dir_uri Stylesheet directory URI.
 236       * @param string $stylesheet         Name of the activated theme's directory.
 237       * @param string $theme_root_uri     Themes root URI.
 238       */
 239      return apply_filters( 'stylesheet_directory_uri', $stylesheet_dir_uri, $stylesheet, $theme_root_uri );
 240  }
 241  
 242  /**
 243   * Retrieves stylesheet URI for the active theme.
 244   *
 245   * The stylesheet file name is 'style.css' which is appended to the stylesheet directory URI path.
 246   * See get_stylesheet_directory_uri().
 247   *
 248   * @since 1.5.0
 249   *
 250   * @return string URI to active theme's stylesheet.
 251   */
 252  function get_stylesheet_uri() {
 253      $stylesheet_dir_uri = get_stylesheet_directory_uri();
 254      $stylesheet_uri     = $stylesheet_dir_uri . '/style.css';
 255      /**
 256       * Filters the URI of the active theme stylesheet.
 257       *
 258       * @since 1.5.0
 259       *
 260       * @param string $stylesheet_uri     Stylesheet URI for the active theme/child theme.
 261       * @param string $stylesheet_dir_uri Stylesheet directory URI for the active theme/child theme.
 262       */
 263      return apply_filters( 'stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
 264  }
 265  
 266  /**
 267   * Retrieves the localized stylesheet URI.
 268   *
 269   * The stylesheet directory for the localized stylesheet files are located, by
 270   * default, in the base theme directory. The name of the locale file will be the
 271   * locale followed by '.css'. If that does not exist, then the text direction
 272   * stylesheet will be checked for existence, for example 'ltr.css'.
 273   *
 274   * The theme may change the location of the stylesheet directory by either using
 275   * the {@see 'stylesheet_directory_uri'} or {@see 'locale_stylesheet_uri'} filters.
 276   *
 277   * If you want to change the location of the stylesheet files for the entire
 278   * WordPress workflow, then change the former. If you just have the locale in a
 279   * separate folder, then change the latter.
 280   *
 281   * @since 2.1.0
 282   *
 283   * @global WP_Locale $wp_locale WordPress date and time locale object.
 284   *
 285   * @return string URI to active theme's localized stylesheet.
 286   */
 287  function get_locale_stylesheet_uri() {
 288      global $wp_locale;
 289      $stylesheet_dir_uri = get_stylesheet_directory_uri();
 290      $dir                = get_stylesheet_directory();
 291      $locale             = get_locale();
 292      if ( file_exists( "$dir/$locale.css" ) ) {
 293          $stylesheet_uri = "$stylesheet_dir_uri/$locale.css";
 294      } elseif ( ! empty( $wp_locale->text_direction ) && file_exists( "$dir/{$wp_locale->text_direction}.css" ) ) {
 295          $stylesheet_uri = "$stylesheet_dir_uri/{$wp_locale->text_direction}.css";
 296      } else {
 297          $stylesheet_uri = '';
 298      }
 299      /**
 300       * Filters the localized stylesheet URI.
 301       *
 302       * @since 2.1.0
 303       *
 304       * @param string $stylesheet_uri     Localized stylesheet URI.
 305       * @param string $stylesheet_dir_uri Stylesheet directory URI.
 306       */
 307      return apply_filters( 'locale_stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
 308  }
 309  
 310  /**
 311   * Retrieves name of the active theme.
 312   *
 313   * @since 1.5.0
 314   *
 315   * @return string Template name.
 316   */
 317  function get_template() {
 318      /**
 319       * Filters the name of the active theme.
 320       *
 321       * @since 1.5.0
 322       *
 323       * @param string $template active theme's directory name.
 324       */
 325      return apply_filters( 'template', get_option( 'template' ) );
 326  }
 327  
 328  /**
 329   * Retrieves template directory path for the active theme.
 330   *
 331   * @since 1.5.0
 332   * @since 6.4.0 Memoizes filter execution so that it only runs once for the current theme.
 333   * @since 6.4.1 Memoization removed.
 334   *
 335   * @return string Path to active theme's template directory.
 336   */
 337  function get_template_directory() {
 338      $template     = get_template();
 339      $theme_root   = get_theme_root( $template );
 340      $template_dir = "$theme_root/$template";
 341  
 342      /**
 343       * Filters the active theme directory path.
 344       *
 345       * @since 1.5.0
 346       *
 347       * @param string $template_dir The path of the active theme directory.
 348       * @param string $template     Directory name of the active theme.
 349       * @param string $theme_root   Absolute path to the themes directory.
 350       */
 351      return apply_filters( 'template_directory', $template_dir, $template, $theme_root );
 352  }
 353  
 354  /**
 355   * Retrieves template directory URI for the active theme.
 356   *
 357   * @since 1.5.0
 358   *
 359   * @return string URI to active theme's template directory.
 360   */
 361  function get_template_directory_uri() {
 362      $template         = str_replace( '%2F', '/', rawurlencode( get_template() ) );
 363      $theme_root_uri   = get_theme_root_uri( $template );
 364      $template_dir_uri = "$theme_root_uri/$template";
 365  
 366      /**
 367       * Filters the active theme directory URI.
 368       *
 369       * @since 1.5.0
 370       *
 371       * @param string $template_dir_uri The URI of the active theme directory.
 372       * @param string $template         Directory name of the active theme.
 373       * @param string $theme_root_uri   The themes root URI.
 374       */
 375      return apply_filters( 'template_directory_uri', $template_dir_uri, $template, $theme_root_uri );
 376  }
 377  
 378  /**
 379   * Retrieves theme roots.
 380   *
 381   * @since 2.9.0
 382   *
 383   * @global string[] $wp_theme_directories
 384   *
 385   * @return array|string An array of theme roots keyed by template/stylesheet
 386   *                      or a single theme root if all themes have the same root.
 387   */
 388  function get_theme_roots() {
 389      global $wp_theme_directories;
 390  
 391      if ( ! is_array( $wp_theme_directories ) || count( $wp_theme_directories ) <= 1 ) {
 392          return '/themes';
 393      }
 394  
 395      $theme_roots = get_site_transient( 'theme_roots' );
 396      if ( false === $theme_roots ) {
 397          search_theme_directories( true ); // Regenerate the transient.
 398          $theme_roots = get_site_transient( 'theme_roots' );
 399      }
 400      return $theme_roots;
 401  }
 402  
 403  /**
 404   * Registers a directory that contains themes.
 405   *
 406   * @since 2.9.0
 407   *
 408   * @global string[] $wp_theme_directories
 409   *
 410   * @param string $directory Either the full filesystem path to a theme folder
 411   *                          or a folder within WP_CONTENT_DIR.
 412   * @return bool True if successfully registered a directory that contains themes,
 413   *              false if the directory does not exist.
 414   */
 415  function register_theme_directory( $directory ) {
 416      global $wp_theme_directories;
 417  
 418      if ( ! file_exists( $directory ) ) {
 419          // Try prepending as the theme directory could be relative to the content directory.
 420          $directory = WP_CONTENT_DIR . '/' . $directory;
 421          // If this directory does not exist, return and do not register.
 422          if ( ! file_exists( $directory ) ) {
 423              return false;
 424          }
 425      }
 426  
 427      if ( ! is_array( $wp_theme_directories ) ) {
 428          $wp_theme_directories = array();
 429      }
 430  
 431      $untrailed = untrailingslashit( $directory );
 432      if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories, true ) ) {
 433          $wp_theme_directories[] = $untrailed;
 434      }
 435  
 436      return true;
 437  }
 438  
 439  /**
 440   * Searches all registered theme directories for complete and valid themes.
 441   *
 442   * @since 2.9.0
 443   *
 444   * @global string[] $wp_theme_directories
 445   *
 446   * @param bool $force Optional. Whether to force a new directory scan. Default false.
 447   * @return array|false Valid themes found on success, false on failure.
 448   */
 449  function search_theme_directories( $force = false ) {
 450      global $wp_theme_directories;
 451      static $found_themes = null;
 452  
 453      if ( empty( $wp_theme_directories ) ) {
 454          return false;
 455      }
 456  
 457      if ( ! $force && isset( $found_themes ) ) {
 458          return $found_themes;
 459      }
 460  
 461      $found_themes = array();
 462  
 463      $wp_theme_directories = (array) $wp_theme_directories;
 464      $relative_theme_roots = array();
 465  
 466      /*
 467       * Set up maybe-relative, maybe-absolute array of theme directories.
 468       * We always want to return absolute, but we need to cache relative
 469       * to use in get_theme_root().
 470       */
 471      foreach ( $wp_theme_directories as $theme_root ) {
 472          if ( str_starts_with( $theme_root, WP_CONTENT_DIR ) ) {
 473              $relative_theme_roots[ str_replace( WP_CONTENT_DIR, '', $theme_root ) ] = $theme_root;
 474          } else {
 475              $relative_theme_roots[ $theme_root ] = $theme_root;
 476          }
 477      }
 478  
 479      /**
 480       * Filters whether to get the cache of the registered theme directories.
 481       *
 482       * @since 3.4.0
 483       *
 484       * @param bool   $cache_expiration Whether to get the cache of the theme directories. Default false.
 485       * @param string $context          The class or function name calling the filter.
 486       */
 487      $cache_expiration = apply_filters( 'wp_cache_themes_persistently', false, 'search_theme_directories' );
 488  
 489      if ( $cache_expiration ) {
 490          $cached_roots = get_site_transient( 'theme_roots' );
 491          if ( is_array( $cached_roots ) ) {
 492              foreach ( $cached_roots as $theme_dir => $theme_root ) {
 493                  // A cached theme root is no longer around, so skip it.
 494                  if ( ! isset( $relative_theme_roots[ $theme_root ] ) ) {
 495                      continue;
 496                  }
 497                  $found_themes[ $theme_dir ] = array(
 498                      'theme_file' => $theme_dir . '/style.css',
 499                      'theme_root' => $relative_theme_roots[ $theme_root ], // Convert relative to absolute.
 500                  );
 501              }
 502              return $found_themes;
 503          }
 504          if ( ! is_int( $cache_expiration ) ) {
 505              $cache_expiration = 30 * MINUTE_IN_SECONDS;
 506          }
 507      } else {
 508          $cache_expiration = 30 * MINUTE_IN_SECONDS;
 509      }
 510  
 511      /* Loop the registered theme directories and extract all themes */
 512      foreach ( $wp_theme_directories as $theme_root ) {
 513  
 514          // Start with directories in the root of the active theme directory.
 515          $dirs = @ scandir( $theme_root );
 516          if ( ! $dirs ) {
 517              wp_trigger_error( __FUNCTION__, "$theme_root is not readable" );
 518              continue;
 519          }
 520          foreach ( $dirs as $dir ) {
 521              if ( ! is_dir( $theme_root . '/' . $dir ) || '.' === $dir[0] || 'CVS' === $dir ) {
 522                  continue;
 523              }
 524              if ( file_exists( $theme_root . '/' . $dir . '/style.css' ) ) {
 525                  /*
 526                   * wp-content/themes/a-single-theme
 527                   * wp-content/themes is $theme_root, a-single-theme is $dir.
 528                   */
 529                  $found_themes[ $dir ] = array(
 530                      'theme_file' => $dir . '/style.css',
 531                      'theme_root' => $theme_root,
 532                  );
 533              } else {
 534                  $found_theme = false;
 535                  /*
 536                   * wp-content/themes/a-folder-of-themes/*
 537                   * wp-content/themes is $theme_root, a-folder-of-themes is $dir, then themes are $sub_dirs.
 538                   */
 539                  $sub_dirs = @ scandir( $theme_root . '/' . $dir );
 540                  if ( ! $sub_dirs ) {
 541                      wp_trigger_error( __FUNCTION__, "$theme_root/$dir is not readable" );
 542                      continue;
 543                  }
 544                  foreach ( $sub_dirs as $sub_dir ) {
 545                      if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || '.' === $dir[0] || 'CVS' === $dir ) {
 546                          continue;
 547                      }
 548                      if ( ! file_exists( $theme_root . '/' . $dir . '/' . $sub_dir . '/style.css' ) ) {
 549                          continue;
 550                      }
 551                      $found_themes[ $dir . '/' . $sub_dir ] = array(
 552                          'theme_file' => $dir . '/' . $sub_dir . '/style.css',
 553                          'theme_root' => $theme_root,
 554                      );
 555                      $found_theme                           = true;
 556                  }
 557                  /*
 558                   * Never mind the above, it's just a theme missing a style.css.
 559                   * Return it; WP_Theme will catch the error.
 560                   */
 561                  if ( ! $found_theme ) {
 562                      $found_themes[ $dir ] = array(
 563                          'theme_file' => $dir . '/style.css',
 564                          'theme_root' => $theme_root,
 565                      );
 566                  }
 567              }
 568          }
 569      }
 570  
 571      asort( $found_themes );
 572  
 573      $theme_roots          = array();
 574      $relative_theme_roots = array_flip( $relative_theme_roots );
 575  
 576      foreach ( $found_themes as $theme_dir => $theme_data ) {
 577          $theme_roots[ $theme_dir ] = $relative_theme_roots[ $theme_data['theme_root'] ]; // Convert absolute to relative.
 578      }
 579  
 580      if ( get_site_transient( 'theme_roots' ) !== $theme_roots ) {
 581          set_site_transient( 'theme_roots', $theme_roots, $cache_expiration );
 582      }
 583  
 584      return $found_themes;
 585  }
 586  
 587  /**
 588   * Retrieves path to themes directory.
 589   *
 590   * Does not have trailing slash.
 591   *
 592   * @since 1.5.0
 593   *
 594   * @global string[] $wp_theme_directories
 595   *
 596   * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
 597   *                                       Default is to leverage the main theme root.
 598   * @return string Themes directory path.
 599   */
 600  function get_theme_root( $stylesheet_or_template = '' ) {
 601      global $wp_theme_directories;
 602  
 603      $theme_root = '';
 604  
 605      if ( $stylesheet_or_template ) {
 606          $theme_root = get_raw_theme_root( $stylesheet_or_template );
 607          if ( $theme_root ) {
 608              /*
 609               * Always prepend WP_CONTENT_DIR unless the root currently registered as a theme directory.
 610               * This gives relative theme roots the benefit of the doubt when things go haywire.
 611               */
 612              if ( ! in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
 613                  $theme_root = WP_CONTENT_DIR . $theme_root;
 614              }
 615          }
 616      }
 617  
 618      if ( ! $theme_root ) {
 619          $theme_root = WP_CONTENT_DIR . '/themes';
 620      }
 621  
 622      /**
 623       * Filters the absolute path to the themes directory.
 624       *
 625       * @since 1.5.0
 626       *
 627       * @param string $theme_root Absolute path to themes directory.
 628       */
 629      return apply_filters( 'theme_root', $theme_root );
 630  }
 631  
 632  /**
 633   * Retrieves URI for themes directory.
 634   *
 635   * Does not have trailing slash.
 636   *
 637   * @since 1.5.0
 638   *
 639   * @global string[] $wp_theme_directories
 640   *
 641   * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
 642   *                                       Default is to leverage the main theme root.
 643   * @param string $theme_root             Optional. The theme root for which calculations will be based,
 644   *                                       preventing the need for a get_raw_theme_root() call. Default empty.
 645   * @return string Themes directory URI.
 646   */
 647  function get_theme_root_uri( $stylesheet_or_template = '', $theme_root = '' ) {
 648      global $wp_theme_directories;
 649  
 650      if ( $stylesheet_or_template && ! $theme_root ) {
 651          $theme_root = get_raw_theme_root( $stylesheet_or_template );
 652      }
 653  
 654      if ( $stylesheet_or_template && $theme_root ) {
 655          if ( in_array( $theme_root, (array) $wp_theme_directories, true ) ) {
 656              // Absolute path. Make an educated guess. YMMV -- but note the filter below.
 657              if ( str_starts_with( $theme_root, WP_CONTENT_DIR ) ) {
 658                  $theme_root_uri = content_url( str_replace( WP_CONTENT_DIR, '', $theme_root ) );
 659              } elseif ( str_starts_with( $theme_root, ABSPATH ) ) {
 660                  $theme_root_uri = site_url( str_replace( ABSPATH, '', $theme_root ) );
 661              } elseif ( str_starts_with( $theme_root, WP_PLUGIN_DIR ) || str_starts_with( $theme_root, WPMU_PLUGIN_DIR ) ) {
 662                  $theme_root_uri = plugins_url( basename( $theme_root ), $theme_root );
 663              } else {
 664                  $theme_root_uri = $theme_root;
 665              }
 666          } else {
 667              $theme_root_uri = content_url( $theme_root );
 668          }
 669      } else {
 670          $theme_root_uri = content_url( 'themes' );
 671      }
 672  
 673      /**
 674       * Filters the URI for themes directory.
 675       *
 676       * @since 1.5.0
 677       *
 678       * @param string $theme_root_uri         The URI for themes directory.
 679       * @param string $siteurl                WordPress web address which is set in General Options.
 680       * @param string $stylesheet_or_template The stylesheet or template name of the theme.
 681       */
 682      return apply_filters( 'theme_root_uri', $theme_root_uri, get_option( 'siteurl' ), $stylesheet_or_template );
 683  }
 684  
 685  /**
 686   * Gets the raw theme root relative to the content directory with no filters applied.
 687   *
 688   * @since 3.1.0
 689   *
 690   * @global string[] $wp_theme_directories
 691   *
 692   * @param string $stylesheet_or_template The stylesheet or template name of the theme.
 693   * @param bool   $skip_cache             Optional. Whether to skip the cache.
 694   *                                       Defaults to false, meaning the cache is used.
 695   * @return string Theme root.
 696   */
 697  function get_raw_theme_root( $stylesheet_or_template, $skip_cache = false ) {
 698      global $wp_theme_directories;
 699  
 700      if ( ! is_array( $wp_theme_directories ) || count( $wp_theme_directories ) <= 1 ) {
 701          return '/themes';
 702      }
 703  
 704      $theme_root = false;
 705  
 706      // If requesting the root for the active theme, consult options to avoid calling get_theme_roots().
 707      if ( ! $skip_cache ) {
 708          if ( get_option( 'stylesheet' ) === $stylesheet_or_template ) {
 709              $theme_root = get_option( 'stylesheet_root' );
 710          } elseif ( get_option( 'template' ) === $stylesheet_or_template ) {
 711              $theme_root = get_option( 'template_root' );
 712          }
 713      }
 714  
 715      if ( empty( $theme_root ) ) {
 716          $theme_roots = get_theme_roots();
 717          if ( ! empty( $theme_roots[ $stylesheet_or_template ] ) ) {
 718              $theme_root = $theme_roots[ $stylesheet_or_template ];
 719          }
 720      }
 721  
 722      return $theme_root;
 723  }
 724  
 725  /**
 726   * Displays localized stylesheet link element.
 727   *
 728   * @since 2.1.0
 729   */
 730  function locale_stylesheet() {
 731      $stylesheet = get_locale_stylesheet_uri();
 732      if ( empty( $stylesheet ) ) {
 733          return;
 734      }
 735  
 736      printf(
 737          '<link rel="stylesheet" href="%s" media="screen" />',
 738          $stylesheet
 739      );
 740  }
 741  
 742  /**
 743   * Switches the theme.
 744   *
 745   * Accepts one argument: $stylesheet of the theme. It also accepts an additional function signature
 746   * of two arguments: $template then $stylesheet. This is for backward compatibility.
 747   *
 748   * @since 2.5.0
 749   *
 750   * @global string[]             $wp_theme_directories
 751   * @global WP_Customize_Manager $wp_customize
 752   * @global array                $sidebars_widgets
 753   * @global array                $wp_registered_sidebars
 754   *
 755   * @param string $stylesheet Stylesheet name.
 756   */
 757  function switch_theme( $stylesheet ) {
 758      global $wp_theme_directories, $wp_customize, $sidebars_widgets, $wp_registered_sidebars;
 759  
 760      $requirements = validate_theme_requirements( $stylesheet );
 761      if ( is_wp_error( $requirements ) ) {
 762          wp_die( $requirements );
 763      }
 764  
 765      $_sidebars_widgets = null;
 766      if ( 'wp_ajax_customize_save' === current_action() ) {
 767          $old_sidebars_widgets_data_setting = $wp_customize->get_setting( 'old_sidebars_widgets_data' );
 768          if ( $old_sidebars_widgets_data_setting ) {
 769              $_sidebars_widgets = $wp_customize->post_value( $old_sidebars_widgets_data_setting );
 770          }
 771      } elseif ( is_array( $sidebars_widgets ) ) {
 772          $_sidebars_widgets = $sidebars_widgets;
 773      }
 774  
 775      if ( is_array( $_sidebars_widgets ) ) {
 776          set_theme_mod(
 777              'sidebars_widgets',
 778              array(
 779                  'time' => time(),
 780                  'data' => $_sidebars_widgets,
 781              )
 782          );
 783      }
 784  
 785      $nav_menu_locations = get_theme_mod( 'nav_menu_locations' );
 786      update_option( 'theme_switch_menu_locations', $nav_menu_locations, true );
 787  
 788      if ( func_num_args() > 1 ) {
 789          $stylesheet = func_get_arg( 1 );
 790      }
 791  
 792      $old_theme = wp_get_theme();
 793      $new_theme = wp_get_theme( $stylesheet );
 794      $template  = $new_theme->get_template();
 795  
 796      if ( wp_is_recovery_mode() ) {
 797          $paused_themes = wp_paused_themes();
 798          $paused_themes->delete( $old_theme->get_stylesheet() );
 799          $paused_themes->delete( $old_theme->get_template() );
 800      }
 801  
 802      update_option( 'template', $template );
 803      update_option( 'stylesheet', $stylesheet );
 804  
 805      if ( count( $wp_theme_directories ) > 1 ) {
 806          update_option( 'template_root', get_raw_theme_root( $template, true ) );
 807          update_option( 'stylesheet_root', get_raw_theme_root( $stylesheet, true ) );
 808      } else {
 809          delete_option( 'template_root' );
 810          delete_option( 'stylesheet_root' );
 811      }
 812  
 813      $new_name = $new_theme->get( 'Name' );
 814  
 815      update_option( 'current_theme', $new_name );
 816  
 817      // Migrate from the old mods_{name} option to theme_mods_{slug}.
 818      if ( is_admin() && false === get_option( 'theme_mods_' . $stylesheet ) ) {
 819          $default_theme_mods = (array) get_option( 'mods_' . $new_name );
 820          if ( ! empty( $nav_menu_locations ) && empty( $default_theme_mods['nav_menu_locations'] ) ) {
 821              $default_theme_mods['nav_menu_locations'] = $nav_menu_locations;
 822          }
 823          add_option( "theme_mods_$stylesheet", $default_theme_mods );
 824      } else {
 825          /*
 826           * Since retrieve_widgets() is called when initializing a theme in the Customizer,
 827           * we need to remove the theme mods to avoid overwriting changes made via
 828           * the Customizer when accessing wp-admin/widgets.php.
 829           */
 830          if ( 'wp_ajax_customize_save' === current_action() ) {
 831              remove_theme_mod( 'sidebars_widgets' );
 832          }
 833      }
 834  
 835      // Stores classic sidebars for later use by block themes.
 836      if ( $new_theme->is_block_theme() ) {
 837          set_theme_mod( 'wp_classic_sidebars', $wp_registered_sidebars );
 838      }
 839  
 840      update_option( 'theme_switched', $old_theme->get_stylesheet() );
 841  
 842      /*
 843       * Reset template globals when switching themes outside of a switched blog
 844       * context to ensure templates will be loaded from the new theme.
 845       */
 846      if ( ! is_multisite() || ! ms_is_switched() ) {
 847          wp_set_template_globals();
 848      }
 849  
 850      // Clear pattern caches.
 851      if ( ! is_multisite() ) {
 852          $new_theme->delete_pattern_cache();
 853          $old_theme->delete_pattern_cache();
 854      }
 855  
 856      // Set autoload=no for the old theme, autoload=yes for the switched theme.
 857      $theme_mods_options = array(
 858          'theme_mods_' . $stylesheet                  => 'yes',
 859          'theme_mods_' . $old_theme->get_stylesheet() => 'no',
 860      );
 861      wp_set_option_autoload_values( $theme_mods_options );
 862  
 863      /**
 864       * Fires after the theme is switched.
 865       *
 866       * See {@see 'after_switch_theme'}.
 867       *
 868       * @since 1.5.0
 869       * @since 4.5.0 Introduced the `$old_theme` parameter.
 870       *
 871       * @param string   $new_name  Name of the new theme.
 872       * @param WP_Theme $new_theme WP_Theme instance of the new theme.
 873       * @param WP_Theme $old_theme WP_Theme instance of the old theme.
 874       */
 875      do_action( 'switch_theme', $new_name, $new_theme, $old_theme );
 876  }
 877  
 878  /**
 879   * Checks that the active theme has the required files.
 880   *
 881   * Standalone themes need to have a `templates/index.html` or `index.php` template file.
 882   * Child themes need to have a `Template` header in the `style.css` stylesheet.
 883   *
 884   * Does not initially check the default theme, which is the fallback and should always exist.
 885   * But if it doesn't exist, it'll fall back to the latest core default theme that does exist.
 886   * Will switch theme to the fallback theme if active theme does not validate.
 887   *
 888   * You can use the {@see 'validate_current_theme'} filter to return false to disable
 889   * this functionality.
 890   *
 891   * @since 1.5.0
 892   * @since 6.0.0 Removed the requirement for block themes to have an `index.php` template.
 893   *
 894   * @see WP_DEFAULT_THEME
 895   *
 896   * @return bool
 897   */
 898  function validate_current_theme() {
 899      /**
 900       * Filters whether to validate the active theme.
 901       *
 902       * @since 2.7.0
 903       *
 904       * @param bool $validate Whether to validate the active theme. Default true.
 905       */
 906      if ( wp_installing() || ! apply_filters( 'validate_current_theme', true ) ) {
 907          return true;
 908      }
 909  
 910      if (
 911          ! file_exists( get_template_directory() . '/templates/index.html' )
 912          && ! file_exists( get_template_directory() . '/block-templates/index.html' ) // Deprecated path support since 5.9.0.
 913          && ! file_exists( get_template_directory() . '/index.php' )
 914      ) {
 915          // Invalid.
 916      } elseif ( ! file_exists( get_template_directory() . '/style.css' ) ) {
 917          // Invalid.
 918      } elseif ( is_child_theme() && ! file_exists( get_stylesheet_directory() . '/style.css' ) ) {
 919          // Invalid.
 920      } else {
 921          // Valid.
 922          return true;
 923      }
 924  
 925      $default = wp_get_theme( WP_DEFAULT_THEME );
 926      if ( $default->exists() ) {
 927          switch_theme( WP_DEFAULT_THEME );
 928          return false;
 929      }
 930  
 931      /**
 932       * If we're in an invalid state but WP_DEFAULT_THEME doesn't exist,
 933       * switch to the latest core default theme that's installed.
 934       *
 935       * If it turns out that this latest core default theme is our current
 936       * theme, then there's nothing we can do about that, so we have to bail,
 937       * rather than going into an infinite loop. (This is why there are
 938       * checks against WP_DEFAULT_THEME above, also.) We also can't do anything
 939       * if it turns out there is no default theme installed. (That's `false`.)
 940       */
 941      $default = WP_Theme::get_core_default_theme();
 942      if ( false === $default || get_stylesheet() === $default->get_stylesheet() ) {
 943          return true;
 944      }
 945  
 946      switch_theme( $default->get_stylesheet() );
 947      return false;
 948  }
 949  
 950  /**
 951   * Validates the theme requirements for WordPress version and PHP version.
 952   *
 953   * Uses the information from `Requires at least` and `Requires PHP` headers
 954   * defined in the theme's `style.css` file.
 955   *
 956   * @since 5.5.0
 957   * @since 5.8.0 Removed support for using `readme.txt` as a fallback.
 958   *
 959   * @param string $stylesheet Directory name for the theme.
 960   * @return true|WP_Error True if requirements are met, WP_Error on failure.
 961   */
 962  function validate_theme_requirements( $stylesheet ) {
 963      $theme = wp_get_theme( $stylesheet );
 964  
 965      $requirements = array(
 966          'requires'     => ! empty( $theme->get( 'RequiresWP' ) ) ? $theme->get( 'RequiresWP' ) : '',
 967          'requires_php' => ! empty( $theme->get( 'RequiresPHP' ) ) ? $theme->get( 'RequiresPHP' ) : '',
 968      );
 969  
 970      $compatible_wp  = is_wp_version_compatible( $requirements['requires'] );
 971      $compatible_php = is_php_version_compatible( $requirements['requires_php'] );
 972  
 973      if ( ! $compatible_wp && ! $compatible_php ) {
 974          return new WP_Error(
 975              'theme_wp_php_incompatible',
 976              sprintf(
 977                  /* translators: %s: Theme name. */
 978                  _x( '<strong>Error:</strong> Current WordPress and PHP versions do not meet minimum requirements for %s.', 'theme' ),
 979                  $theme->display( 'Name' )
 980              )
 981          );
 982      } elseif ( ! $compatible_php ) {
 983          return new WP_Error(
 984              'theme_php_incompatible',
 985              sprintf(
 986                  /* translators: %s: Theme name. */
 987                  _x( '<strong>Error:</strong> Current PHP version does not meet minimum requirements for %s.', 'theme' ),
 988                  $theme->display( 'Name' )
 989              )
 990          );
 991      } elseif ( ! $compatible_wp ) {
 992          return new WP_Error(
 993              'theme_wp_incompatible',
 994              sprintf(
 995                  /* translators: %s: Theme name. */
 996                  _x( '<strong>Error:</strong> Current WordPress version does not meet minimum requirements for %s.', 'theme' ),
 997                  $theme->display( 'Name' )
 998              )
 999          );
1000      }
1001  
1002      /**
1003       * Filters the theme requirement validation response.
1004       *
1005       * If a theme fails due to a Core-provided validation (incompatible WP, PHP versions), this
1006       * filter will not fire. A WP_Error response will already be returned.
1007       *
1008       * This filter is intended to add additional validation steps by site administrators.
1009       *
1010       * @since 6.9.0
1011       *
1012       * @param bool|WP_Error $met_requirements True if the theme meets requirements, WP_Error if not.
1013       * @param string        $stylesheet       Directory name for the theme.
1014       */
1015      return apply_filters( 'validate_theme_requirements', true, $stylesheet );
1016  }
1017  
1018  /**
1019   * Retrieves all theme modifications.
1020   *
1021   * @since 3.1.0
1022   * @since 5.9.0 The return value is always an array.
1023   *
1024   * @return array Theme modifications.
1025   */
1026  function get_theme_mods() {
1027      $theme_slug = get_option( 'stylesheet' );
1028      $mods       = get_option( "theme_mods_$theme_slug" );
1029  
1030      if ( false === $mods ) {
1031          $theme_name = get_option( 'current_theme' );
1032          if ( false === $theme_name ) {
1033              $theme_name = wp_get_theme()->get( 'Name' );
1034          }
1035  
1036          $mods = get_option( "mods_$theme_name" ); // Deprecated location.
1037          if ( is_admin() && false !== $mods ) {
1038              update_option( "theme_mods_$theme_slug", $mods );
1039              delete_option( "mods_$theme_name" );
1040          }
1041      }
1042  
1043      if ( ! is_array( $mods ) ) {
1044          $mods = array();
1045      }
1046  
1047      return $mods;
1048  }
1049  
1050  /**
1051   * Retrieves theme modification value for the active theme.
1052   *
1053   * If the modification name does not exist and `$default_value` is a string, then the
1054   * default will be passed through the {@link https://www.php.net/sprintf sprintf()}
1055   * PHP function with the template directory URI as the first value and the
1056   * stylesheet directory URI as the second value.
1057   *
1058   * @since 2.1.0
1059   *
1060   * @param string $name          Theme modification name.
1061   * @param mixed  $default_value Optional. Theme modification default value. Default false.
1062   * @return mixed Theme modification value.
1063   */
1064  function get_theme_mod( $name, $default_value = false ) {
1065      $mods = get_theme_mods();
1066  
1067      if ( isset( $mods[ $name ] ) ) {
1068          /**
1069           * Filters the theme modification, or 'theme_mod', value.
1070           *
1071           * The dynamic portion of the hook name, `$name`, refers to the key name
1072           * of the modification array. For example, 'header_textcolor', 'header_image',
1073           * and so on depending on the theme options.
1074           *
1075           * @since 2.2.0
1076           *
1077           * @param mixed $current_mod The value of the active theme modification.
1078           */
1079          return apply_filters( "theme_mod_{$name}", $mods[ $name ] );
1080      }
1081  
1082      if ( is_string( $default_value ) ) {
1083          // Only run the replacement if an sprintf() string format pattern was found.
1084          if ( preg_match( '#(?<!%)%(?:\d+\$?)?s#', $default_value ) ) {
1085              // Remove a single trailing percent sign.
1086              $default_value = preg_replace( '#(?<!%)%$#', '', $default_value );
1087              $default_value = sprintf( $default_value, get_template_directory_uri(), get_stylesheet_directory_uri() );
1088          }
1089      }
1090  
1091      /** This filter is documented in wp-includes/theme.php */
1092      return apply_filters( "theme_mod_{$name}", $default_value );
1093  }
1094  
1095  /**
1096   * Updates theme modification value for the active theme.
1097   *
1098   * @since 2.1.0
1099   * @since 5.6.0 A return value was added.
1100   *
1101   * @param string $name  Theme modification name.
1102   * @param mixed  $value Theme modification value.
1103   * @return bool True if the value was updated, false otherwise.
1104   */
1105  function set_theme_mod( $name, $value ) {
1106      $mods      = get_theme_mods();
1107      $old_value = $mods[ $name ] ?? false;
1108  
1109      /**
1110       * Filters the theme modification, or 'theme_mod', value on save.
1111       *
1112       * The dynamic portion of the hook name, `$name`, refers to the key name
1113       * of the modification array. For example, 'header_textcolor', 'header_image',
1114       * and so on depending on the theme options.
1115       *
1116       * @since 3.9.0
1117       *
1118       * @param mixed $value     The new value of the theme modification.
1119       * @param mixed $old_value The current value of the theme modification.
1120       */
1121      $mods[ $name ] = apply_filters( "pre_set_theme_mod_{$name}", $value, $old_value );
1122  
1123      $theme = get_option( 'stylesheet' );
1124  
1125      return update_option( "theme_mods_$theme", $mods );
1126  }
1127  
1128  /**
1129   * Removes theme modification name from active theme list.
1130   *
1131   * If removing the name also removes all elements, then the entire option
1132   * will be removed.
1133   *
1134   * @since 2.1.0
1135   *
1136   * @param string $name Theme modification name.
1137   */
1138  function remove_theme_mod( $name ) {
1139      $mods = get_theme_mods();
1140  
1141      if ( ! isset( $mods[ $name ] ) ) {
1142          return;
1143      }
1144  
1145      unset( $mods[ $name ] );
1146  
1147      if ( empty( $mods ) ) {
1148          remove_theme_mods();
1149          return;
1150      }
1151  
1152      $theme = get_option( 'stylesheet' );
1153  
1154      update_option( "theme_mods_$theme", $mods );
1155  }
1156  
1157  /**
1158   * Removes theme modifications option for the active theme.
1159   *
1160   * @since 2.1.0
1161   */
1162  function remove_theme_mods() {
1163      delete_option( 'theme_mods_' . get_option( 'stylesheet' ) );
1164  
1165      // Old style.
1166      $theme_name = get_option( 'current_theme' );
1167      if ( false === $theme_name ) {
1168          $theme_name = wp_get_theme()->get( 'Name' );
1169      }
1170  
1171      delete_option( 'mods_' . $theme_name );
1172  }
1173  
1174  /**
1175   * Retrieves the custom header text color in 3- or 6-digit hexadecimal form.
1176   *
1177   * @since 2.1.0
1178   *
1179   * @return string Header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
1180   */
1181  function get_header_textcolor() {
1182      return get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
1183  }
1184  
1185  /**
1186   * Displays the custom header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
1187   *
1188   * @since 2.1.0
1189   */
1190  function header_textcolor() {
1191      echo get_header_textcolor();
1192  }
1193  
1194  /**
1195   * Whether to display the header text.
1196   *
1197   * @since 3.4.0
1198   *
1199   * @return bool
1200   */
1201  function display_header_text() {
1202      if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
1203          return false;
1204      }
1205  
1206      $text_color = get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
1207      return 'blank' !== $text_color;
1208  }
1209  
1210  /**
1211   * Checks whether a header image is set or not.
1212   *
1213   * @since 4.2.0
1214   *
1215   * @see get_header_image()
1216   *
1217   * @return bool Whether a header image is set or not.
1218   */
1219  function has_header_image() {
1220      return (bool) get_header_image();
1221  }
1222  
1223  /**
1224   * Retrieves header image for custom header.
1225   *
1226   * @since 2.1.0
1227   *
1228   * @return string|false
1229   */
1230  function get_header_image() {
1231      $url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1232  
1233      if ( 'remove-header' === $url ) {
1234          return false;
1235      }
1236  
1237      if ( is_random_header_image() ) {
1238          $url = get_random_header_image();
1239      }
1240  
1241      /**
1242       * Filters the header image URL.
1243       *
1244       * @since 6.1.0
1245       *
1246       * @param string $url Header image URL.
1247       */
1248      $url = apply_filters( 'get_header_image', $url );
1249  
1250      if ( ! is_string( $url ) ) {
1251          return false;
1252      }
1253  
1254      $url = trim( $url );
1255      return sanitize_url( set_url_scheme( $url ) );
1256  }
1257  
1258  /**
1259   * Creates image tag markup for a custom header image.
1260   *
1261   * @since 4.4.0
1262   *
1263   * @param array $attr Optional. Additional attributes for the image tag. Can be used
1264   *                              to override the default attributes. Default empty.
1265   * @return string HTML image element markup or empty string on failure.
1266   */
1267  function get_header_image_tag( $attr = array() ) {
1268      $header      = get_custom_header();
1269      $header->url = get_header_image();
1270  
1271      if ( ! $header->url ) {
1272          return '';
1273      }
1274  
1275      $width  = absint( $header->width );
1276      $height = absint( $header->height );
1277      $alt    = '';
1278  
1279      // Use alternative text assigned to the image, if available. Otherwise, leave it empty.
1280      if ( ! empty( $header->attachment_id ) ) {
1281          $image_alt = get_post_meta( $header->attachment_id, '_wp_attachment_image_alt', true );
1282  
1283          if ( is_string( $image_alt ) ) {
1284              $alt = $image_alt;
1285          }
1286      }
1287  
1288      $attr = wp_parse_args(
1289          $attr,
1290          array(
1291              'src'    => $header->url,
1292              'width'  => $width,
1293              'height' => $height,
1294              'alt'    => $alt,
1295          )
1296      );
1297  
1298      // Generate 'srcset' and 'sizes' if not already present.
1299      if ( empty( $attr['srcset'] ) && ! empty( $header->attachment_id ) ) {
1300          $image_meta = get_post_meta( $header->attachment_id, '_wp_attachment_metadata', true );
1301          $size_array = array( $width, $height );
1302  
1303          if ( is_array( $image_meta ) ) {
1304              $srcset = wp_calculate_image_srcset( $size_array, $header->url, $image_meta, $header->attachment_id );
1305  
1306              if ( ! empty( $attr['sizes'] ) ) {
1307                  $sizes = $attr['sizes'];
1308              } else {
1309                  $sizes = wp_calculate_image_sizes( $size_array, $header->url, $image_meta, $header->attachment_id );
1310              }
1311  
1312              if ( $srcset && $sizes ) {
1313                  $attr['srcset'] = $srcset;
1314                  $attr['sizes']  = $sizes;
1315              }
1316          }
1317      }
1318  
1319      $attr = array_merge(
1320          $attr,
1321          wp_get_loading_optimization_attributes( 'img', $attr, 'get_header_image_tag' )
1322      );
1323  
1324      /*
1325       * If the default value of `lazy` for the `loading` attribute is overridden
1326       * to omit the attribute for this image, ensure it is not included.
1327       */
1328      if ( isset( $attr['loading'] ) && ! $attr['loading'] ) {
1329          unset( $attr['loading'] );
1330      }
1331  
1332      // If the `fetchpriority` attribute is overridden and set to false or an empty string.
1333      if ( isset( $attr['fetchpriority'] ) && ! $attr['fetchpriority'] ) {
1334          unset( $attr['fetchpriority'] );
1335      }
1336  
1337      // If the `decoding` attribute is overridden and set to false or an empty string.
1338      if ( isset( $attr['decoding'] ) && ! $attr['decoding'] ) {
1339          unset( $attr['decoding'] );
1340      }
1341  
1342      /**
1343       * Filters the list of header image attributes.
1344       *
1345       * @since 5.9.0
1346       *
1347       * @param array  $attr   Array of the attributes for the image tag.
1348       * @param object $header The custom header object returned by 'get_custom_header()'.
1349       */
1350      $attr = apply_filters( 'get_header_image_tag_attributes', $attr, $header );
1351  
1352      $attr = array_map( 'esc_attr', $attr );
1353      $html = '<img';
1354  
1355      foreach ( $attr as $name => $value ) {
1356          $html .= ' ' . $name . '="' . $value . '"';
1357      }
1358  
1359      $html .= ' />';
1360  
1361      /**
1362       * Filters the markup of header images.
1363       *
1364       * @since 4.4.0
1365       *
1366       * @param string $html   The HTML image tag markup being filtered.
1367       * @param object $header The custom header object returned by 'get_custom_header()'.
1368       * @param array  $attr   Array of the attributes for the image tag.
1369       */
1370      return apply_filters( 'get_header_image_tag', $html, $header, $attr );
1371  }
1372  
1373  /**
1374   * Displays the image markup for a custom header image.
1375   *
1376   * @since 4.4.0
1377   *
1378   * @param array $attr Optional. Attributes for the image markup. Default empty.
1379   */
1380  function the_header_image_tag( $attr = array() ) {
1381      echo get_header_image_tag( $attr );
1382  }
1383  
1384  /**
1385   * Gets random header image data from registered images in theme.
1386   *
1387   * @since 3.4.0
1388   *
1389   * @access private
1390   *
1391   * @global array $_wp_default_headers
1392   *
1393   * @return object
1394   */
1395  function _get_random_header_data() {
1396      global $_wp_default_headers;
1397      static $_wp_random_header = null;
1398  
1399      if ( empty( $_wp_random_header ) ) {
1400          $header_image_mod = get_theme_mod( 'header_image', '' );
1401          $headers          = array();
1402  
1403          if ( 'random-uploaded-image' === $header_image_mod ) {
1404              $headers = get_uploaded_header_images();
1405          } elseif ( ! empty( $_wp_default_headers ) ) {
1406              if ( 'random-default-image' === $header_image_mod ) {
1407                  $headers = $_wp_default_headers;
1408              } else {
1409                  if ( current_theme_supports( 'custom-header', 'random-default' ) ) {
1410                      $headers = $_wp_default_headers;
1411                  }
1412              }
1413          }
1414  
1415          if ( empty( $headers ) ) {
1416              return new stdClass();
1417          }
1418  
1419          $_wp_random_header = (object) $headers[ array_rand( $headers ) ];
1420  
1421          $_wp_random_header->url = sprintf(
1422              $_wp_random_header->url,
1423              get_template_directory_uri(),
1424              get_stylesheet_directory_uri()
1425          );
1426  
1427          $_wp_random_header->thumbnail_url = sprintf(
1428              $_wp_random_header->thumbnail_url,
1429              get_template_directory_uri(),
1430              get_stylesheet_directory_uri()
1431          );
1432      }
1433  
1434      return $_wp_random_header;
1435  }
1436  
1437  /**
1438   * Gets random header image URL from registered images in theme.
1439   *
1440   * @since 3.2.0
1441   *
1442   * @return string Path to header image.
1443   */
1444  function get_random_header_image() {
1445      $random_image = _get_random_header_data();
1446  
1447      if ( empty( $random_image->url ) ) {
1448          return '';
1449      }
1450  
1451      return $random_image->url;
1452  }
1453  
1454  /**
1455   * Checks if random header image is in use.
1456   *
1457   * Always true if user expressly chooses the option in Appearance > Header.
1458   * Also true if theme has multiple header images registered, no specific header image
1459   * is chosen, and theme turns on random headers with add_theme_support().
1460   *
1461   * @since 3.2.0
1462   *
1463   * @param string $type The random pool to use. Possible values include 'any',
1464   *                     'default', 'uploaded'. Default 'any'.
1465   * @return bool
1466   */
1467  function is_random_header_image( $type = 'any' ) {
1468      $header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1469  
1470      if ( 'any' === $type ) {
1471          if ( 'random-default-image' === $header_image_mod
1472              || 'random-uploaded-image' === $header_image_mod
1473              || ( empty( $header_image_mod ) && '' !== get_random_header_image() )
1474          ) {
1475              return true;
1476          }
1477      } else {
1478          if ( "random-$type-image" === $header_image_mod ) {
1479              return true;
1480          } elseif ( 'default' === $type
1481              && empty( $header_image_mod ) && '' !== get_random_header_image()
1482          ) {
1483              return true;
1484          }
1485      }
1486  
1487      return false;
1488  }
1489  
1490  /**
1491   * Displays header image URL.
1492   *
1493   * @since 2.1.0
1494   */
1495  function header_image() {
1496      $image = get_header_image();
1497  
1498      if ( $image ) {
1499          echo esc_url( $image );
1500      }
1501  }
1502  
1503  /**
1504   * Gets the header images uploaded for the active theme.
1505   *
1506   * @since 3.2.0
1507   *
1508   * @return array
1509   */
1510  function get_uploaded_header_images() {
1511      $header_images = array();
1512  
1513      $headers = get_posts(
1514          array(
1515              'post_type'  => 'attachment',
1516              'meta_key'   => '_wp_attachment_is_custom_header',
1517              'meta_value' => get_option( 'stylesheet' ),
1518              'orderby'    => 'none',
1519              'nopaging'   => true,
1520          )
1521      );
1522  
1523      if ( empty( $headers ) ) {
1524          return array();
1525      }
1526  
1527      foreach ( (array) $headers as $header ) {
1528          $url          = sanitize_url( wp_get_attachment_url( $header->ID ) );
1529          $header_data  = wp_get_attachment_metadata( $header->ID );
1530          $header_index = $header->ID;
1531  
1532          $header_images[ $header_index ]                      = array();
1533          $header_images[ $header_index ]['attachment_id']     = $header->ID;
1534          $header_images[ $header_index ]['url']               = $url;
1535          $header_images[ $header_index ]['thumbnail_url']     = $url;
1536          $header_images[ $header_index ]['alt_text']          = get_post_meta( $header->ID, '_wp_attachment_image_alt', true );
1537          $header_images[ $header_index ]['attachment_parent'] = $header_data['attachment_parent'] ?? '';
1538  
1539          if ( isset( $header_data['width'] ) ) {
1540              $header_images[ $header_index ]['width'] = $header_data['width'];
1541          }
1542          if ( isset( $header_data['height'] ) ) {
1543              $header_images[ $header_index ]['height'] = $header_data['height'];
1544          }
1545      }
1546  
1547      return $header_images;
1548  }
1549  
1550  /**
1551   * Gets the header image data.
1552   *
1553   * @since 3.4.0
1554   * @since 7.1.1 The `width` and `height` are cast to non-negative integers.
1555   *
1556   * @global array $_wp_default_headers
1557   *
1558   * @return object
1559   */
1560  function get_custom_header() {
1561      global $_wp_default_headers;
1562  
1563      if ( is_random_header_image() ) {
1564          $data = _get_random_header_data();
1565      } else {
1566          $data = get_theme_mod( 'header_image_data' );
1567          if ( ! $data && current_theme_supports( 'custom-header', 'default-image' ) ) {
1568              $directory_args        = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
1569              $data                  = array();
1570              $data['url']           = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
1571              $data['thumbnail_url'] = $data['url'];
1572              if ( ! empty( $_wp_default_headers ) ) {
1573                  foreach ( (array) $_wp_default_headers as $default_header ) {
1574                      $url = vsprintf( $default_header['url'], $directory_args );
1575                      if ( $data['url'] === $url ) {
1576                          $data                  = $default_header;
1577                          $data['url']           = $url;
1578                          $data['thumbnail_url'] = vsprintf( $data['thumbnail_url'], $directory_args );
1579                          break;
1580                      }
1581                  }
1582              }
1583          }
1584      }
1585  
1586      $default = array(
1587          'url'           => '',
1588          'thumbnail_url' => '',
1589          'width'         => get_theme_support( 'custom-header', 'width' ),
1590          'height'        => get_theme_support( 'custom-header', 'height' ),
1591          'video'         => get_theme_support( 'custom-header', 'video' ),
1592      );
1593  
1594      if ( ! is_array( $data ) && ! is_object( $data ) ) {
1595          $data = array();
1596      }
1597      $header         = (object) wp_parse_args( $data, $default );
1598      $header->width  = absint( $header->width );
1599      $header->height = absint( $header->height );
1600      return $header;
1601  }
1602  
1603  /**
1604   * Registers a selection of default headers to be displayed by the custom header admin UI.
1605   *
1606   * @since 3.0.0
1607   *
1608   * @global array $_wp_default_headers
1609   *
1610   * @param array $headers Array of headers keyed by a string ID. The IDs point to arrays
1611   *                       containing 'url', 'thumbnail_url', and 'description' keys.
1612   */
1613  function register_default_headers( $headers ) {
1614      global $_wp_default_headers;
1615  
1616      $_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
1617  }
1618  
1619  /**
1620   * Unregisters default headers.
1621   *
1622   * This function must be called after register_default_headers() has already added the
1623   * header you want to remove.
1624   *
1625   * @see register_default_headers()
1626   * @since 3.0.0
1627   *
1628   * @global array $_wp_default_headers
1629   *
1630   * @param string|array $header The header string id (key of array) to remove, or an array thereof.
1631   * @return bool|null A single header returns true on success, false on failure.
1632   *                   There is currently no return value for multiple headers.
1633   */
1634  function unregister_default_headers( $header ) {
1635      global $_wp_default_headers;
1636  
1637      if ( is_array( $header ) ) {
1638          array_map( 'unregister_default_headers', $header );
1639          return null;
1640      } elseif ( isset( $_wp_default_headers[ $header ] ) ) {
1641          unset( $_wp_default_headers[ $header ] );
1642          return true;
1643      } else {
1644          return false;
1645      }
1646  }
1647  
1648  /**
1649   * Checks whether a header video is set or not.
1650   *
1651   * @since 4.7.0
1652   *
1653   * @see get_header_video_url()
1654   *
1655   * @return bool Whether a header video is set or not.
1656   */
1657  function has_header_video() {
1658      return (bool) get_header_video_url();
1659  }
1660  
1661  /**
1662   * Retrieves header video URL for custom header.
1663   *
1664   * Uses a local video if present, or falls back to an external video.
1665   *
1666   * @since 4.7.0
1667   *
1668   * @return string|false Header video URL or false if there is no video.
1669   */
1670  function get_header_video_url() {
1671      $id = absint( get_theme_mod( 'header_video' ) );
1672  
1673      if ( $id ) {
1674          // Get the file URL from the attachment ID.
1675          $url = wp_get_attachment_url( $id );
1676      } else {
1677          $url = get_theme_mod( 'external_header_video' );
1678      }
1679  
1680      /**
1681       * Filters the header video URL.
1682       *
1683       * @since 4.7.3
1684       *
1685       * @param string $url Header video URL, if available.
1686       */
1687      $url = apply_filters( 'get_header_video_url', $url );
1688  
1689      if ( ! $id && ! $url ) {
1690          return false;
1691      }
1692  
1693      return sanitize_url( set_url_scheme( $url ) );
1694  }
1695  
1696  /**
1697   * Displays header video URL.
1698   *
1699   * @since 4.7.0
1700   */
1701  function the_header_video_url() {
1702      $video = get_header_video_url();
1703  
1704      if ( $video ) {
1705          echo esc_url( $video );
1706      }
1707  }
1708  
1709  /**
1710   * Retrieves header video settings.
1711   *
1712   * @since 4.7.0
1713   *
1714   * @return array
1715   */
1716  function get_header_video_settings() {
1717      $header     = get_custom_header();
1718      $video_url  = get_header_video_url();
1719      $video_type = wp_check_filetype( $video_url, wp_get_mime_types() );
1720  
1721      $settings = array(
1722          'mimeType'  => '',
1723          'posterUrl' => get_header_image(),
1724          'videoUrl'  => $video_url,
1725          'width'     => absint( $header->width ),
1726          'height'    => absint( $header->height ),
1727          'minWidth'  => 900,
1728          'minHeight' => 500,
1729          'l10n'      => array(
1730              'pause'      => __( 'Pause' ),
1731              'play'       => __( 'Play' ),
1732              'pauseSpeak' => __( 'Video is paused.' ),
1733              'playSpeak'  => __( 'Video is playing.' ),
1734          ),
1735      );
1736  
1737      if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {
1738          $settings['mimeType'] = 'video/x-youtube';
1739      } elseif ( ! empty( $video_type['type'] ) ) {
1740          $settings['mimeType'] = $video_type['type'];
1741      }
1742  
1743      /**
1744       * Filters header video settings.
1745       *
1746       * @since 4.7.0
1747       *
1748       * @param array $settings An array of header video settings.
1749       */
1750      return apply_filters( 'header_video_settings', $settings );
1751  }
1752  
1753  /**
1754   * Checks whether a custom header is set or not.
1755   *
1756   * @since 4.7.0
1757   *
1758   * @return bool True if a custom header is set. False if not.
1759   */
1760  function has_custom_header() {
1761      if ( has_header_image() || ( has_header_video() && is_header_video_active() ) ) {
1762          return true;
1763      }
1764  
1765      return false;
1766  }
1767  
1768  /**
1769   * Checks whether the custom header video is eligible to show on the current page.
1770   *
1771   * @since 4.7.0
1772   *
1773   * @return bool True if the custom header video should be shown. False if not.
1774   */
1775  function is_header_video_active() {
1776      if ( ! get_theme_support( 'custom-header', 'video' ) ) {
1777          return false;
1778      }
1779  
1780      $video_active_cb = get_theme_support( 'custom-header', 'video-active-callback' );
1781  
1782      if ( empty( $video_active_cb ) || ! is_callable( $video_active_cb ) ) {
1783          $show_video = true;
1784      } else {
1785          $show_video = call_user_func( $video_active_cb );
1786      }
1787  
1788      /**
1789       * Filters whether the custom header video is eligible to show on the current page.
1790       *
1791       * @since 4.7.0
1792       *
1793       * @param bool $show_video Whether the custom header video should be shown. Returns the value
1794       *                         of the theme setting for the `custom-header`'s `video-active-callback`.
1795       *                         If no callback is set, the default value is that of `is_front_page()`.
1796       */
1797      return apply_filters( 'is_header_video_active', $show_video );
1798  }
1799  
1800  /**
1801   * Retrieves the markup for a custom header.
1802   *
1803   * The container div will always be returned in the Customizer preview.
1804   *
1805   * @since 4.7.0
1806   *
1807   * @return string The markup for a custom header on success.
1808   */
1809  function get_custom_header_markup() {
1810      if ( ! has_custom_header() && ! is_customize_preview() ) {
1811          return '';
1812      }
1813  
1814      return sprintf(
1815          '<div id="wp-custom-header" class="wp-custom-header">%s</div>',
1816          get_header_image_tag()
1817      );
1818  }
1819  
1820  /**
1821   * Prints the markup for a custom header.
1822   *
1823   * A container div will always be printed in the Customizer preview.
1824   *
1825   * @since 4.7.0
1826   */
1827  function the_custom_header_markup() {
1828      $custom_header = get_custom_header_markup();
1829      if ( empty( $custom_header ) ) {
1830          return;
1831      }
1832  
1833      echo $custom_header;
1834  
1835      if ( is_header_video_active() && ( has_header_video() || is_customize_preview() ) ) {
1836          wp_enqueue_script( 'wp-custom-header' );
1837          wp_localize_script( 'wp-custom-header', '_wpCustomHeaderSettings', get_header_video_settings() );
1838      }
1839  }
1840  
1841  /**
1842   * Retrieves background image for custom background.
1843   *
1844   * @since 3.0.0
1845   *
1846   * @return string
1847   */
1848  function get_background_image() {
1849      return get_theme_mod( 'background_image', get_theme_support( 'custom-background', 'default-image' ) );
1850  }
1851  
1852  /**
1853   * Displays background image path.
1854   *
1855   * @since 3.0.0
1856   */
1857  function background_image() {
1858      echo get_background_image();
1859  }
1860  
1861  /**
1862   * Retrieves value for custom background color.
1863   *
1864   * @since 3.0.0
1865   *
1866   * @return string
1867   */
1868  function get_background_color() {
1869      return get_theme_mod( 'background_color', get_theme_support( 'custom-background', 'default-color' ) );
1870  }
1871  
1872  /**
1873   * Displays background color value.
1874   *
1875   * @since 3.0.0
1876   */
1877  function background_color() {
1878      echo get_background_color();
1879  }
1880  
1881  /**
1882   * Default custom background callback.
1883   *
1884   * @since 3.0.0
1885   */
1886  function _custom_background_cb() {
1887      // $background is the saved custom image, or the default image.
1888      $background = set_url_scheme( get_background_image() );
1889  
1890      /*
1891       * $color is the saved custom color.
1892       * A default has to be specified in style.css. It will not be printed here.
1893       */
1894      $color = get_background_color();
1895  
1896      if ( get_theme_support( 'custom-background', 'default-color' ) === $color ) {
1897          $color = false;
1898      }
1899  
1900      if ( ! $background && ! $color ) {
1901          if ( is_customize_preview() ) {
1902              echo '<style id="custom-background-css"></style>';
1903          }
1904          return;
1905      }
1906  
1907      $style = $color ? 'background-color: ' . maybe_hash_hex_color( $color ) . ';' : '';
1908  
1909      if ( $background ) {
1910          $image = ' background-image: url("' . sanitize_url( $background ) . '");';
1911  
1912          // Background Position.
1913          $position_x = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) );
1914          $position_y = get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) );
1915  
1916          if ( ! in_array( $position_x, array( 'left', 'center', 'right' ), true ) ) {
1917              $position_x = 'left';
1918          }
1919  
1920          if ( ! in_array( $position_y, array( 'top', 'center', 'bottom' ), true ) ) {
1921              $position_y = 'top';
1922          }
1923  
1924          $position = " background-position: $position_x $position_y;";
1925  
1926          // Background Size.
1927          $size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) );
1928  
1929          if ( ! in_array( $size, array( 'auto', 'contain', 'cover' ), true ) ) {
1930              $size = 'auto';
1931          }
1932  
1933          $size = " background-size: $size;";
1934  
1935          // Background Repeat.
1936          $repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) );
1937  
1938          if ( ! in_array( $repeat, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) {
1939              $repeat = 'repeat';
1940          }
1941  
1942          $repeat = " background-repeat: $repeat;";
1943  
1944          // Background Scroll.
1945          $attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) );
1946  
1947          if ( 'fixed' !== $attachment ) {
1948              $attachment = 'scroll';
1949          }
1950  
1951          $attachment = " background-attachment: $attachment;";
1952  
1953          $style .= $image . $position . $size . $repeat . $attachment;
1954      }
1955  
1956      $processor = new WP_HTML_Tag_Processor( '<style id="custom-background-css"></style>' );
1957      $processor->next_tag();
1958  
1959      $style_tag_content = 'body.custom-background { ' . trim( $style ) . ' }';
1960      $processor->set_modifiable_text( "\n{$style_tag_content}\n" );
1961      echo "{$processor->get_updated_html()}\n";
1962  }
1963  
1964  /**
1965   * Renders the Custom CSS style element.
1966   *
1967   * @since 4.7.0
1968   */
1969  function wp_custom_css_cb() {
1970      $styles = wp_get_custom_css();
1971      if ( ! $styles && ! is_customize_preview() ) {
1972          return;
1973      }
1974  
1975      $processor = new WP_HTML_Tag_Processor( '<style></style>' );
1976      $processor->next_tag();
1977      $processor->set_attribute( 'id', 'wp-custom-css' );
1978      $processor->set_modifiable_text( "\n{$styles}\n" );
1979      echo "{$processor->get_updated_html()}\n";
1980  }
1981  
1982  /**
1983   * Fetches the `custom_css` post for a given theme.
1984   *
1985   * @since 4.7.0
1986   *
1987   * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the active theme.
1988   * @return WP_Post|null The custom_css post or null if none exists.
1989   */
1990  function wp_get_custom_css_post( $stylesheet = '' ) {
1991      if ( empty( $stylesheet ) ) {
1992          $stylesheet = get_stylesheet();
1993      }
1994  
1995      $custom_css_query_vars = array(
1996          'post_type'              => 'custom_css',
1997          'post_status'            => get_post_stati(),
1998          'name'                   => sanitize_title( $stylesheet ),
1999          'posts_per_page'         => 1,
2000          'no_found_rows'          => true,
2001          'cache_results'          => true,
2002          'update_post_meta_cache' => false,
2003          'update_post_term_cache' => false,
2004          'lazy_load_term_meta'    => false,
2005      );
2006  
2007      $post = null;
2008      if ( get_stylesheet() === $stylesheet ) {
2009          $post_id = get_theme_mod( 'custom_css_post_id' );
2010  
2011          if ( $post_id > 0 && get_post( $post_id ) ) {
2012              $post = get_post( $post_id );
2013          }
2014  
2015          // `-1` indicates no post exists; no query necessary.
2016          if ( ! $post && -1 !== $post_id ) {
2017              $query = new WP_Query( $custom_css_query_vars );
2018              $post  = $query->post;
2019              /*
2020               * Cache the lookup. See wp_update_custom_css_post().
2021               * @todo This should get cleared if a custom_css post is added/removed.
2022               */
2023              set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
2024          }
2025      } else {
2026          $query = new WP_Query( $custom_css_query_vars );
2027          $post  = $query->post;
2028      }
2029  
2030      return $post;
2031  }
2032  
2033  /**
2034   * Fetches the saved Custom CSS content for rendering.
2035   *
2036   * @since 4.7.0
2037   *
2038   * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the active theme.
2039   * @return string The Custom CSS Post content.
2040   */
2041  function wp_get_custom_css( $stylesheet = '' ) {
2042      $css = '';
2043  
2044      if ( empty( $stylesheet ) ) {
2045          $stylesheet = get_stylesheet();
2046      }
2047  
2048      $post = wp_get_custom_css_post( $stylesheet );
2049      if ( $post ) {
2050          $css = $post->post_content;
2051      }
2052  
2053      /**
2054       * Filters the custom CSS output into the head element.
2055       *
2056       * @since 4.7.0
2057       *
2058       * @param string $css        CSS pulled in from the Custom CSS post type.
2059       * @param string $stylesheet The theme stylesheet name.
2060       */
2061      $css = apply_filters( 'wp_get_custom_css', $css, $stylesheet );
2062  
2063      return $css;
2064  }
2065  
2066  /**
2067   * Updates the `custom_css` post for a given theme.
2068   *
2069   * Inserts a `custom_css` post when one doesn't yet exist.
2070   *
2071   * @since 4.7.0
2072   *
2073   * @param string $css CSS, stored in `post_content`.
2074   * @param array  $args {
2075   *     Args.
2076   *
2077   *     @type string $preprocessed Optional. Pre-processed CSS, stored in `post_content_filtered`.
2078   *                                Normally empty string.
2079   *     @type string $stylesheet   Optional. Stylesheet (child theme) to update.
2080   *                                Defaults to active theme/stylesheet.
2081   * }
2082   * @return WP_Post|WP_Error Post on success, error on failure.
2083   */
2084  function wp_update_custom_css_post( $css, $args = array() ) {
2085      $args = wp_parse_args(
2086          $args,
2087          array(
2088              'preprocessed' => '',
2089              'stylesheet'   => get_stylesheet(),
2090          )
2091      );
2092  
2093      $data = array(
2094          'css'          => $css,
2095          'preprocessed' => $args['preprocessed'],
2096      );
2097  
2098      /**
2099       * Filters the `css` (`post_content`) and `preprocessed` (`post_content_filtered`) args
2100       * for a `custom_css` post being updated.
2101       *
2102       * This filter can be used by plugin that offer CSS pre-processors, to store the original
2103       * pre-processed CSS in `post_content_filtered` and then store processed CSS in `post_content`.
2104       * When used in this way, the `post_content_filtered` should be supplied as the setting value
2105       * instead of `post_content` via a the `customize_value_custom_css` filter, for example:
2106       *
2107       * <code>
2108       * add_filter( 'customize_value_custom_css', function( $value, $setting ) {
2109       *     $post = wp_get_custom_css_post( $setting->stylesheet );
2110       *     if ( $post && ! empty( $post->post_content_filtered ) ) {
2111       *         $css = $post->post_content_filtered;
2112       *     }
2113       *     return $css;
2114       * }, 10, 2 );
2115       * </code>
2116       *
2117       * @since 4.7.0
2118       * @param array $data {
2119       *     Custom CSS data.
2120       *
2121       *     @type string $css          CSS stored in `post_content`.
2122       *     @type string $preprocessed Pre-processed CSS stored in `post_content_filtered`.
2123       *                                Normally empty string.
2124       * }
2125       * @param array $args {
2126       *     The args passed into `wp_update_custom_css_post()` merged with defaults.
2127       *
2128       *     @type string $css          The original CSS passed in to be updated.
2129       *     @type string $preprocessed The original preprocessed CSS passed in to be updated.
2130       *     @type string $stylesheet   The stylesheet (theme) being updated.
2131       * }
2132       */
2133      $data = apply_filters( 'update_custom_css_data', $data, array_merge( $args, compact( 'css' ) ) );
2134  
2135      $post_data = array(
2136          'post_title'            => $args['stylesheet'],
2137          'post_name'             => sanitize_title( $args['stylesheet'] ),
2138          'post_type'             => 'custom_css',
2139          'post_status'           => 'publish',
2140          'post_content'          => $data['css'],
2141          'post_content_filtered' => $data['preprocessed'],
2142      );
2143  
2144      // Update post if it already exists, otherwise create a new one.
2145      $post = wp_get_custom_css_post( $args['stylesheet'] );
2146      if ( $post ) {
2147          $post_data['ID'] = $post->ID;
2148          $r               = wp_update_post( wp_slash( $post_data ), true );
2149      } else {
2150          $r = wp_insert_post( wp_slash( $post_data ), true );
2151  
2152          if ( ! is_wp_error( $r ) ) {
2153              if ( get_stylesheet() === $args['stylesheet'] ) {
2154                  set_theme_mod( 'custom_css_post_id', $r );
2155              }
2156  
2157              // Trigger creation of a revision. This should be removed once #30854 is resolved.
2158              $revisions = wp_get_latest_revision_id_and_total_count( $r );
2159              if ( ! is_wp_error( $revisions ) && 0 === $revisions['count'] ) {
2160                  wp_save_post_revision( $r );
2161              }
2162          }
2163      }
2164  
2165      if ( is_wp_error( $r ) ) {
2166          return $r;
2167      }
2168      return get_post( $r );
2169  }
2170  
2171  /**
2172   * Adds callback for custom TinyMCE editor stylesheets.
2173   *
2174   * The parameter $stylesheet is the name of the stylesheet, relative to
2175   * the theme root. It also accepts an array of stylesheets.
2176   * It is optional and defaults to 'editor-style.css'.
2177   *
2178   * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
2179   * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
2180   * If an array of stylesheets is passed to add_editor_style(),
2181   * RTL is only added for the first stylesheet.
2182   *
2183   * Since version 3.4 the TinyMCE body has .rtl CSS class.
2184   * It is a better option to use that class and add any RTL styles to the main stylesheet.
2185   *
2186   * Internally this registers the 'editor-style' (singular) theme support to enable
2187   * editor stylesheets for the classic/TinyMCE editor.
2188   *
2189   * The plural form, 'editor-styles', is a separate theme support that allows the block
2190   * editor to load the stylesheets registered by this function. To add styles to the
2191   * block editor, use add_theme_support( 'editor-styles' ) in addition to this function.
2192   * Block themes support 'editor-styles' by default.
2193   *
2194   * @since 3.0.0
2195   *
2196   * @global array $editor_styles
2197   *
2198   * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
2199   *                                 Defaults to 'editor-style.css'
2200   */
2201  function add_editor_style( $stylesheet = 'editor-style.css' ) {
2202      global $editor_styles;
2203  
2204      add_theme_support( 'editor-style' );
2205  
2206      $editor_styles = (array) $editor_styles;
2207      $stylesheet    = (array) $stylesheet;
2208  
2209      if ( is_rtl() ) {
2210          $rtl_stylesheet = str_replace( '.css', '-rtl.css', $stylesheet[0] );
2211          $stylesheet[]   = $rtl_stylesheet;
2212      }
2213  
2214      $editor_styles = array_merge( $editor_styles, $stylesheet );
2215  }
2216  
2217  /**
2218   * Removes all visual editor stylesheets.
2219   *
2220   * Internally this removes the 'editor-style' (singular) theme support added by
2221   * add_editor_style(). In the admin, it also clears the registered stylesheets,
2222   * so they are no longer loaded in either the classic/TinyMCE or the block editor.
2223   *
2224   * The plural form, 'editor-styles', is a separate theme support and is not affected
2225   * by this function. To remove it, use remove_theme_support( 'editor-styles' ).
2226   *
2227   * @since 3.1.0
2228   *
2229   * @global array $editor_styles
2230   *
2231   * @return bool True on success, false if there were no stylesheets to remove.
2232   */
2233  function remove_editor_styles() {
2234      if ( ! current_theme_supports( 'editor-style' ) ) {
2235          return false;
2236      }
2237      _remove_theme_support( 'editor-style' );
2238      if ( is_admin() ) {
2239          $GLOBALS['editor_styles'] = array();
2240      }
2241      return true;
2242  }
2243  
2244  /**
2245   * Retrieves any registered editor stylesheet URLs.
2246   *
2247   * @since 4.0.0
2248   *
2249   * @global array $editor_styles Registered editor stylesheets
2250   *
2251   * @return string[] If registered, a list of editor stylesheet URLs.
2252   */
2253  function get_editor_stylesheets() {
2254      $stylesheets = array();
2255      // Load editor_style.css if the active theme supports it.
2256      if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
2257          $editor_styles = $GLOBALS['editor_styles'];
2258  
2259          $editor_styles = array_unique( array_filter( $editor_styles ) );
2260          $style_uri     = get_stylesheet_directory_uri();
2261          $style_dir     = get_stylesheet_directory();
2262  
2263          // Support externally referenced styles (like, say, fonts).
2264          foreach ( $editor_styles as $key => $file ) {
2265              if ( preg_match( '~^(https?:)?//~', $file ) ) {
2266                  $stylesheets[] = sanitize_url( $file );
2267                  unset( $editor_styles[ $key ] );
2268              }
2269          }
2270  
2271          // Look in a parent theme first, that way child theme CSS overrides.
2272          if ( is_child_theme() ) {
2273              $template_uri = get_template_directory_uri();
2274              $template_dir = get_template_directory();
2275  
2276              foreach ( $editor_styles as $key => $file ) {
2277                  if ( $file && file_exists( "$template_dir/$file" ) ) {
2278                      $stylesheets[] = "$template_uri/$file";
2279                  }
2280              }
2281          }
2282  
2283          foreach ( $editor_styles as $file ) {
2284              if ( $file && file_exists( "$style_dir/$file" ) ) {
2285                  $stylesheets[] = "$style_uri/$file";
2286              }
2287          }
2288      }
2289  
2290      /**
2291       * Filters the array of URLs of stylesheets applied to the editor.
2292       *
2293       * @since 4.3.0
2294       *
2295       * @param string[] $stylesheets Array of URLs of stylesheets to be applied to the editor.
2296       */
2297      return apply_filters( 'editor_stylesheets', $stylesheets );
2298  }
2299  
2300  /**
2301   * Expands a theme's starter content configuration using core-provided data.
2302   *
2303   * @since 4.7.0
2304   *
2305   * @return array Array of starter content.
2306   */
2307  function get_theme_starter_content() {
2308      $theme_support = get_theme_support( 'starter-content' );
2309      if ( is_array( $theme_support ) && ! empty( $theme_support[0] ) && is_array( $theme_support[0] ) ) {
2310          $config = $theme_support[0];
2311      } else {
2312          $config = array();
2313      }
2314  
2315      $core_content = array(
2316          'widgets'   => array(
2317              'text_business_info' => array(
2318                  'text',
2319                  array(
2320                      'title'  => _x( 'Find Us', 'Theme starter content' ),
2321                      'text'   => implode(
2322                          '',
2323                          array(
2324                              '<strong>' . _x( 'Address', 'Theme starter content' ) . "</strong>\n",
2325                              _x( '123 Main Street', 'Theme starter content' ) . "\n",
2326                              _x( 'New York, NY 10001', 'Theme starter content' ) . "\n\n",
2327                              '<strong>' . _x( 'Hours', 'Theme starter content' ) . "</strong>\n",
2328                              _x( 'Monday&ndash;Friday: 9:00AM&ndash;5:00PM', 'Theme starter content' ) . "\n",
2329                              _x( 'Saturday &amp; Sunday: 11:00AM&ndash;3:00PM', 'Theme starter content' ),
2330                          )
2331                      ),
2332                      'filter' => true,
2333                      'visual' => true,
2334                  ),
2335              ),
2336              'text_about'         => array(
2337                  'text',
2338                  array(
2339                      'title'  => _x( 'About This Site', 'Theme starter content' ),
2340                      'text'   => _x( 'This may be a good place to introduce yourself and your site or include some credits.', 'Theme starter content' ),
2341                      'filter' => true,
2342                      'visual' => true,
2343                  ),
2344              ),
2345              'archives'           => array(
2346                  'archives',
2347                  array(
2348                      'title' => _x( 'Archives', 'Theme starter content' ),
2349                  ),
2350              ),
2351              'calendar'           => array(
2352                  'calendar',
2353                  array(
2354                      'title' => _x( 'Calendar', 'Theme starter content' ),
2355                  ),
2356              ),
2357              'categories'         => array(
2358                  'categories',
2359                  array(
2360                      'title' => _x( 'Categories', 'Theme starter content' ),
2361                  ),
2362              ),
2363              'meta'               => array(
2364                  'meta',
2365                  array(
2366                      'title' => _x( 'Meta', 'Theme starter content' ),
2367                  ),
2368              ),
2369              'recent-comments'    => array(
2370                  'recent-comments',
2371                  array(
2372                      'title' => _x( 'Recent Comments', 'Theme starter content' ),
2373                  ),
2374              ),
2375              'recent-posts'       => array(
2376                  'recent-posts',
2377                  array(
2378                      'title' => _x( 'Recent Posts', 'Theme starter content' ),
2379                  ),
2380              ),
2381              'search'             => array(
2382                  'search',
2383                  array(
2384                      'title' => _x( 'Search', 'Theme starter content' ),
2385                  ),
2386              ),
2387          ),
2388          'nav_menus' => array(
2389              'link_home'       => array(
2390                  'type'  => 'custom',
2391                  'title' => _x( 'Home', 'Theme starter content' ),
2392                  'url'   => home_url( '/' ),
2393              ),
2394              'page_home'       => array( // Deprecated in favor of 'link_home'.
2395                  'type'      => 'post_type',
2396                  'object'    => 'page',
2397                  'object_id' => '{{home}}',
2398              ),
2399              'page_about'      => array(
2400                  'type'      => 'post_type',
2401                  'object'    => 'page',
2402                  'object_id' => '{{about}}',
2403              ),
2404              'page_blog'       => array(
2405                  'type'      => 'post_type',
2406                  'object'    => 'page',
2407                  'object_id' => '{{blog}}',
2408              ),
2409              'page_news'       => array(
2410                  'type'      => 'post_type',
2411                  'object'    => 'page',
2412                  'object_id' => '{{news}}',
2413              ),
2414              'page_contact'    => array(
2415                  'type'      => 'post_type',
2416                  'object'    => 'page',
2417                  'object_id' => '{{contact}}',
2418              ),
2419  
2420              'link_email'      => array(
2421                  'title' => _x( 'Email', 'Theme starter content' ),
2422                  'url'   => 'mailto:wordpress@example.com',
2423              ),
2424              'link_facebook'   => array(
2425                  'title' => _x( 'Facebook', 'Theme starter content' ),
2426                  'url'   => 'https://www.facebook.com/wordpress',
2427              ),
2428              'link_foursquare' => array(
2429                  'title' => _x( 'Foursquare', 'Theme starter content' ),
2430                  'url'   => 'https://foursquare.com/',
2431              ),
2432              'link_github'     => array(
2433                  'title' => _x( 'GitHub', 'Theme starter content' ),
2434                  'url'   => 'https://github.com/wordpress/',
2435              ),
2436              'link_instagram'  => array(
2437                  'title' => _x( 'Instagram', 'Theme starter content' ),
2438                  'url'   => 'https://www.instagram.com/explore/tags/wordcamp/',
2439              ),
2440              'link_linkedin'   => array(
2441                  'title' => _x( 'LinkedIn', 'Theme starter content' ),
2442                  'url'   => 'https://www.linkedin.com/company/1089783',
2443              ),
2444              'link_pinterest'  => array(
2445                  'title' => _x( 'Pinterest', 'Theme starter content' ),
2446                  'url'   => 'https://www.pinterest.com/',
2447              ),
2448              'link_twitter'    => array(
2449                  'title' => _x( 'Twitter', 'Theme starter content' ),
2450                  'url'   => 'https://twitter.com/wordpress',
2451              ),
2452              'link_yelp'       => array(
2453                  'title' => _x( 'Yelp', 'Theme starter content' ),
2454                  'url'   => 'https://www.yelp.com',
2455              ),
2456              'link_youtube'    => array(
2457                  'title' => _x( 'YouTube', 'Theme starter content' ),
2458                  'url'   => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
2459              ),
2460          ),
2461          'posts'     => array(
2462              'home'             => array(
2463                  'post_type'    => 'page',
2464                  'post_title'   => _x( 'Home', 'Theme starter content' ),
2465                  'post_content' => sprintf(
2466                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2467                      _x( 'Welcome to your site! This is your homepage, which is what most visitors will see when they come to your site for the first time.', 'Theme starter content' )
2468                  ),
2469              ),
2470              'about'            => array(
2471                  'post_type'    => 'page',
2472                  'post_title'   => _x( 'About', 'Theme starter content' ),
2473                  'post_content' => sprintf(
2474                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2475                      _x( 'You might be an artist who would like to introduce yourself and your work here or maybe you are a business with a mission to describe.', 'Theme starter content' )
2476                  ),
2477              ),
2478              'contact'          => array(
2479                  'post_type'    => 'page',
2480                  'post_title'   => _x( 'Contact', 'Theme starter content' ),
2481                  'post_content' => sprintf(
2482                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2483                      _x( 'This is a page with some basic contact information, such as an address and phone number. You might also try a plugin to add a contact form.', 'Theme starter content' )
2484                  ),
2485              ),
2486              'blog'             => array(
2487                  'post_type'  => 'page',
2488                  'post_title' => _x( 'Blog', 'Theme starter content' ),
2489              ),
2490              'news'             => array(
2491                  'post_type'  => 'page',
2492                  'post_title' => _x( 'News', 'Theme starter content' ),
2493              ),
2494  
2495              'homepage-section' => array(
2496                  'post_type'    => 'page',
2497                  'post_title'   => _x( 'A homepage section', 'Theme starter content' ),
2498                  'post_content' => sprintf(
2499                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2500                      _x( 'This is an example of a homepage section. Homepage sections can be any page other than the homepage itself, including the page that shows your latest blog posts.', 'Theme starter content' )
2501                  ),
2502              ),
2503          ),
2504      );
2505  
2506      $content = array();
2507  
2508      foreach ( $config as $type => $args ) {
2509          switch ( $type ) {
2510              // Use options and theme_mods as-is.
2511              case 'options':
2512              case 'theme_mods':
2513                  $content[ $type ] = $config[ $type ];
2514                  break;
2515  
2516              // Widgets are grouped into sidebars.
2517              case 'widgets':
2518                  foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
2519                      foreach ( $widgets as $id => $widget ) {
2520                          if ( is_array( $widget ) ) {
2521  
2522                              // Item extends core content.
2523                              if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2524                                  $widget = array(
2525                                      $core_content[ $type ][ $id ][0],
2526                                      array_merge( $core_content[ $type ][ $id ][1], $widget ),
2527                                  );
2528                              }
2529  
2530                              $content[ $type ][ $sidebar_id ][] = $widget;
2531                          } elseif ( is_string( $widget )
2532                              && ! empty( $core_content[ $type ] )
2533                              && ! empty( $core_content[ $type ][ $widget ] )
2534                          ) {
2535                              $content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
2536                          }
2537                      }
2538                  }
2539                  break;
2540  
2541              // And nav menu items are grouped into nav menus.
2542              case 'nav_menus':
2543                  foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
2544  
2545                      // Ensure nav menus get a name.
2546                      if ( empty( $nav_menu['name'] ) ) {
2547                          $nav_menu['name'] = $nav_menu_location;
2548                      }
2549  
2550                      $content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
2551  
2552                      foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
2553                          if ( is_array( $nav_menu_item ) ) {
2554  
2555                              // Item extends core content.
2556                              if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2557                                  $nav_menu_item = array_merge( $core_content[ $type ][ $id ], $nav_menu_item );
2558                              }
2559  
2560                              $content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
2561                          } elseif ( is_string( $nav_menu_item )
2562                              && ! empty( $core_content[ $type ] )
2563                              && ! empty( $core_content[ $type ][ $nav_menu_item ] )
2564                          ) {
2565                              $content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
2566                          }
2567                      }
2568                  }
2569                  break;
2570  
2571              // Attachments are posts but have special treatment.
2572              case 'attachments':
2573                  foreach ( $config[ $type ] as $id => $item ) {
2574                      if ( ! empty( $item['file'] ) ) {
2575                          $content[ $type ][ $id ] = $item;
2576                      }
2577                  }
2578                  break;
2579  
2580              /*
2581               * All that's left now are posts (besides attachments).
2582               * Not a default case for the sake of clarity and future work.
2583               */
2584              case 'posts':
2585                  foreach ( $config[ $type ] as $id => $item ) {
2586                      if ( is_array( $item ) ) {
2587  
2588                          // Item extends core content.
2589                          if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2590                              $item = array_merge( $core_content[ $type ][ $id ], $item );
2591                          }
2592  
2593                          // Enforce a subset of fields.
2594                          $content[ $type ][ $id ] = wp_array_slice_assoc(
2595                              $item,
2596                              array(
2597                                  'post_type',
2598                                  'post_title',
2599                                  'post_excerpt',
2600                                  'post_name',
2601                                  'post_content',
2602                                  'menu_order',
2603                                  'comment_status',
2604                                  'thumbnail',
2605                                  'template',
2606                              )
2607                          );
2608                      } elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
2609                          $content[ $type ][ $item ] = $core_content[ $type ][ $item ];
2610                      }
2611                  }
2612                  break;
2613          }
2614      }
2615  
2616      /**
2617       * Filters the expanded array of starter content.
2618       *
2619       * @since 4.7.0
2620       *
2621       * @param array $content Array of starter content.
2622       * @param array $config  Array of theme-specific starter content configuration.
2623       */
2624      return apply_filters( 'get_theme_starter_content', $content, $config );
2625  }
2626  
2627  /**
2628   * Registers theme support for a given feature.
2629   *
2630   * Must be called in the theme's functions.php file to work.
2631   * If attached to a hook, it must be {@see 'after_setup_theme'}.
2632   * The {@see 'init'} hook may be too late for some features.
2633   *
2634   * Example usage:
2635   *
2636   *     add_theme_support( 'title-tag' );
2637   *     add_theme_support( 'custom-logo', array(
2638   *         'height' => 480,
2639   *         'width'  => 720,
2640   *     ) );
2641   *
2642   * @since 2.9.0
2643   * @since 3.4.0 The `custom-header-uploads` feature was deprecated.
2644   * @since 3.6.0 The `html5` feature was added.
2645   * @since 3.6.1 The `html5` feature requires an array of types to be passed. Defaults to
2646   *              'comment-list', 'comment-form', 'search-form' for backward compatibility.
2647   * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'.
2648   * @since 4.1.0 The `title-tag` feature was added.
2649   * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added.
2650   * @since 4.7.0 The `starter-content` feature was added.
2651   * @since 5.0.0 The `responsive-embeds`, `align-wide`, `dark-editor-style`, `disable-custom-colors`,
2652   *              `disable-custom-font-sizes`, `editor-color-palette`, `editor-font-sizes`,
2653   *              `editor-styles`, and `wp-block-styles` features were added.
2654   * @since 5.3.0 The `html5` feature now also accepts 'script' and 'style'.
2655   * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
2656   *              by adding it to the function signature.
2657   * @since 5.4.0 The `disable-custom-gradients` feature limits to default gradients or gradients added
2658   *              through `editor-gradient-presets` theme support.
2659   * @since 5.5.0 The `core-block-patterns` feature was added and is enabled by default.
2660   * @since 5.5.0 The `custom-logo` feature now also accepts 'unlink-homepage-logo'.
2661   * @since 5.6.0 The `post-formats` feature warns if no array is passed as the second parameter.
2662   * @since 5.8.0 The `widgets-block-editor` feature enables the Widgets block editor.
2663   * @since 5.8.0 The `block-templates` feature indicates whether a theme uses block-based templates.
2664   * @since 6.0.0 The `html5` feature warns if no array is passed as the second parameter.
2665   * @since 6.1.0 The `block-template-parts` feature allows to edit any reusable template part from site editor.
2666   * @since 6.1.0 The `disable-layout-styles` feature disables the default layout styles.
2667   * @since 6.3.0 The `link-color` feature allows to enable the link color setting.
2668   * @since 6.3.0 The `border` feature allows themes without theme.json to add border styles to blocks.
2669   * @since 6.5.0 The `appearance-tools` feature enables a few design tools for blocks,
2670   *              see `WP_Theme_JSON::APPEARANCE_TOOLS_OPT_INS` for a complete list.
2671   * @since 6.6.0 The `editor-spacing-sizes` feature was added.
2672   * @since 7.0.0 The `html5` feature's 'script' and 'style' arguments are deprecated and unused.
2673   *
2674   * @global array $_wp_theme_features
2675   *
2676   * @param string $feature The feature being added. Likely core values include:
2677   *                          - 'admin-bar'
2678   *                          - 'align-wide'
2679   *                          - 'appearance-tools'
2680   *                          - 'automatic-feed-links'
2681   *                          - 'block-templates'
2682   *                          - 'block-template-parts'
2683   *                          - 'border'
2684   *                          - 'core-block-patterns'
2685   *                          - 'custom-background'
2686   *                          - 'custom-header'
2687   *                          - 'custom-line-height'
2688   *                          - 'custom-logo'
2689   *                          - 'customize-selective-refresh-widgets'
2690   *                          - 'custom-spacing'
2691   *                          - 'custom-units'
2692   *                          - 'dark-editor-style'
2693   *                          - 'disable-custom-colors'
2694   *                          - 'disable-custom-font-sizes'
2695   *                          - 'disable-custom-gradients'
2696   *                          - 'disable-layout-styles'
2697   *                          - 'editor-color-palette'
2698   *                          - 'editor-gradient-presets'
2699   *                          - 'editor-font-sizes'
2700   *                          - 'editor-spacing-sizes'
2701   *                          - 'editor-styles'
2702   *                          - 'featured-content'
2703   *                          - 'html5'
2704   *                          - 'link-color'
2705   *                          - 'menus'
2706   *                          - 'post-formats'
2707   *                          - 'post-thumbnails'
2708   *                          - 'responsive-embeds'
2709   *                          - 'starter-content'
2710   *                          - 'title-tag'
2711   *                          - 'widgets'
2712   *                          - 'widgets-block-editor'
2713   *                          - 'wp-block-styles'
2714   * @param mixed  ...$args Optional extra arguments to pass along with certain features.
2715   * @return void|false Void on success, false on failure.
2716   */
2717  function add_theme_support( $feature, ...$args ) {
2718      global $_wp_theme_features;
2719  
2720      if ( ! $args ) {
2721          $args = true;
2722      }
2723  
2724      switch ( $feature ) {
2725          case 'post-thumbnails':
2726              // All post types are already supported.
2727              if ( true === get_theme_support( 'post-thumbnails' ) ) {
2728                  return;
2729              }
2730  
2731              /*
2732               * Merge post types with any that already declared their support
2733               * for post thumbnails.
2734               */
2735              if ( isset( $args[0] ) && is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
2736                  $args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
2737              }
2738  
2739              break;
2740  
2741          case 'post-formats':
2742              if ( isset( $args[0] ) && is_array( $args[0] ) ) {
2743                  $post_formats = get_post_format_slugs();
2744                  unset( $post_formats['standard'] );
2745  
2746                  $args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
2747              } else {
2748                  _doing_it_wrong(
2749                      "add_theme_support( 'post-formats' )",
2750                      __( 'You need to pass an array of post formats.' ),
2751                      '5.6.0'
2752                  );
2753                  return false;
2754              }
2755              break;
2756  
2757          case 'html5':
2758              // You can't just pass 'html5', you need to pass an array of types.
2759              if ( empty( $args[0] ) || ! is_array( $args[0] ) ) {
2760                  _doing_it_wrong(
2761                      "add_theme_support( 'html5' )",
2762                      __( 'You need to pass an array of types.' ),
2763                      '3.6.1'
2764                  );
2765  
2766                  if ( ! empty( $args[0] ) && ! is_array( $args[0] ) ) {
2767                      return false;
2768                  }
2769  
2770                  // Build an array of types for back-compat.
2771                  $args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
2772              }
2773  
2774              // Calling 'html5' again merges, rather than overwrites.
2775              if ( isset( $_wp_theme_features['html5'] ) ) {
2776                  $args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
2777              }
2778              break;
2779  
2780          case 'custom-logo':
2781              if ( true === $args ) {
2782                  $args = array( 0 => array() );
2783              }
2784              $defaults = array(
2785                  'width'                => null,
2786                  'height'               => null,
2787                  'flex-width'           => false,
2788                  'flex-height'          => false,
2789                  'header-text'          => '',
2790                  'unlink-homepage-logo' => false,
2791              );
2792              $args[0]  = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
2793  
2794              // Allow full flexibility if no size is specified.
2795              if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
2796                  $args[0]['flex-width']  = true;
2797                  $args[0]['flex-height'] = true;
2798              }
2799              break;
2800  
2801          case 'custom-header-uploads':
2802              return add_theme_support( 'custom-header', array( 'uploads' => true ) );
2803  
2804          case 'custom-header':
2805              if ( true === $args ) {
2806                  $args = array( 0 => array() );
2807              }
2808  
2809              $defaults = array(
2810                  'default-image'          => '',
2811                  'random-default'         => false,
2812                  'width'                  => 0,
2813                  'height'                 => 0,
2814                  'flex-height'            => false,
2815                  'flex-width'             => false,
2816                  'default-text-color'     => '',
2817                  'header-text'            => true,
2818                  'uploads'                => true,
2819                  'wp-head-callback'       => '',
2820                  'admin-head-callback'    => '',
2821                  'admin-preview-callback' => '',
2822                  'video'                  => false,
2823                  'video-active-callback'  => 'is_front_page',
2824              );
2825  
2826              $jit = isset( $args[0]['__jit'] );
2827              unset( $args[0]['__jit'] );
2828  
2829              /*
2830               * Merge in data from previous add_theme_support() calls.
2831               * The first value registered wins. (A child theme is set up first.)
2832               */
2833              if ( isset( $_wp_theme_features['custom-header'] ) ) {
2834                  $args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
2835              }
2836  
2837              /*
2838               * Load in the defaults at the end, as we need to insure first one wins.
2839               * This will cause all constants to be defined, as each arg will then be set to the default.
2840               */
2841              if ( $jit ) {
2842                  $args[0] = wp_parse_args( $args[0], $defaults );
2843              }
2844  
2845              /*
2846               * If a constant was defined, use that value. Otherwise, define the constant to ensure
2847               * the constant is always accurate (and is not defined later,  overriding our value).
2848               * As stated above, the first value wins.
2849               * Once we get to wp_loaded (just-in-time), define any constants we haven't already.
2850               * Constants should be avoided. Don't reference them. This is just for backward compatibility.
2851               */
2852  
2853              if ( defined( 'NO_HEADER_TEXT' ) ) {
2854                  $args[0]['header-text'] = ! NO_HEADER_TEXT;
2855              } elseif ( isset( $args[0]['header-text'] ) ) {
2856                  define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
2857              }
2858  
2859              if ( defined( 'HEADER_IMAGE_WIDTH' ) ) {
2860                  $args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
2861              } elseif ( isset( $args[0]['width'] ) ) {
2862                  define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
2863              }
2864  
2865              if ( defined( 'HEADER_IMAGE_HEIGHT' ) ) {
2866                  $args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
2867              } elseif ( isset( $args[0]['height'] ) ) {
2868                  define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
2869              }
2870  
2871              if ( defined( 'HEADER_TEXTCOLOR' ) ) {
2872                  $args[0]['default-text-color'] = HEADER_TEXTCOLOR;
2873              } elseif ( isset( $args[0]['default-text-color'] ) ) {
2874                  define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
2875              }
2876  
2877              if ( defined( 'HEADER_IMAGE' ) ) {
2878                  $args[0]['default-image'] = HEADER_IMAGE;
2879              } elseif ( isset( $args[0]['default-image'] ) ) {
2880                  define( 'HEADER_IMAGE', $args[0]['default-image'] );
2881              }
2882  
2883              if ( $jit && ! empty( $args[0]['default-image'] ) ) {
2884                  $args[0]['random-default'] = false;
2885              }
2886  
2887              /*
2888               * If headers are supported, and we still don't have a defined width or height,
2889               * we have implicit flex sizes.
2890               */
2891              if ( $jit ) {
2892                  if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) ) {
2893                      $args[0]['flex-width'] = true;
2894                  }
2895                  if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) ) {
2896                      $args[0]['flex-height'] = true;
2897                  }
2898              }
2899  
2900              break;
2901  
2902          case 'custom-background':
2903              if ( true === $args ) {
2904                  $args = array( 0 => array() );
2905              }
2906  
2907              $defaults = array(
2908                  'default-image'          => '',
2909                  'default-preset'         => 'default',
2910                  'default-position-x'     => 'left',
2911                  'default-position-y'     => 'top',
2912                  'default-size'           => 'auto',
2913                  'default-repeat'         => 'repeat',
2914                  'default-attachment'     => 'scroll',
2915                  'default-color'          => '',
2916                  'wp-head-callback'       => '_custom_background_cb',
2917                  'admin-head-callback'    => '',
2918                  'admin-preview-callback' => '',
2919              );
2920  
2921              $jit = isset( $args[0]['__jit'] );
2922              unset( $args[0]['__jit'] );
2923  
2924              // Merge in data from previous add_theme_support() calls. The first value registered wins.
2925              if ( isset( $_wp_theme_features['custom-background'] ) ) {
2926                  $args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
2927              }
2928  
2929              if ( $jit ) {
2930                  $args[0] = wp_parse_args( $args[0], $defaults );
2931              }
2932  
2933              if ( defined( 'BACKGROUND_COLOR' ) ) {
2934                  $args[0]['default-color'] = BACKGROUND_COLOR;
2935              } elseif ( isset( $args[0]['default-color'] ) || $jit ) {
2936                  define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
2937              }
2938  
2939              if ( defined( 'BACKGROUND_IMAGE' ) ) {
2940                  $args[0]['default-image'] = BACKGROUND_IMAGE;
2941              } elseif ( isset( $args[0]['default-image'] ) || $jit ) {
2942                  define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
2943              }
2944  
2945              break;
2946  
2947          // Ensure that 'title-tag' is accessible in the admin.
2948          case 'title-tag':
2949              // Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
2950              if ( did_action( 'wp_loaded' ) ) {
2951                  _doing_it_wrong(
2952                      "add_theme_support( 'title-tag' )",
2953                      sprintf(
2954                          /* translators: 1: title-tag, 2: wp_loaded */
2955                          __( 'Theme support for %1$s should be registered before the %2$s hook.' ),
2956                          '<code>title-tag</code>',
2957                          '<code>wp_loaded</code>'
2958                      ),
2959                      '4.1.0'
2960                  );
2961  
2962                  return false;
2963              }
2964      }
2965  
2966      $_wp_theme_features[ $feature ] = $args;
2967  }
2968  
2969  /**
2970   * Registers the internal custom header and background routines.
2971   *
2972   * @since 3.4.0
2973   * @access private
2974   *
2975   * @global Custom_Image_Header $custom_image_header
2976   * @global Custom_Background   $custom_background
2977   */
2978  function _custom_header_background_just_in_time() {
2979      global $custom_image_header, $custom_background;
2980  
2981      if ( current_theme_supports( 'custom-header' ) ) {
2982          // In case any constants were defined after an add_custom_image_header() call, re-run.
2983          add_theme_support( 'custom-header', array( '__jit' => true ) );
2984  
2985          $args = get_theme_support( 'custom-header' );
2986          if ( $args[0]['wp-head-callback'] ) {
2987              add_action( 'wp_head', $args[0]['wp-head-callback'] );
2988          }
2989  
2990          if ( is_admin() ) {
2991              require_once  ABSPATH . 'wp-admin/includes/class-custom-image-header.php';
2992              $custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2993          }
2994      }
2995  
2996      if ( current_theme_supports( 'custom-background' ) ) {
2997          // In case any constants were defined after an add_custom_background() call, re-run.
2998          add_theme_support( 'custom-background', array( '__jit' => true ) );
2999  
3000          $args = get_theme_support( 'custom-background' );
3001          add_action( 'wp_head', $args[0]['wp-head-callback'] );
3002  
3003          if ( is_admin() ) {
3004              require_once  ABSPATH . 'wp-admin/includes/class-custom-background.php';
3005              $custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
3006          }
3007      }
3008  }
3009  
3010  /**
3011   * Adds CSS to hide header text for custom logo, based on Customizer setting.
3012   *
3013   * @since 4.5.0
3014   * @access private
3015   */
3016  function _custom_logo_header_styles() {
3017      if ( ! current_theme_supports( 'custom-header', 'header-text' )
3018          && get_theme_support( 'custom-logo', 'header-text' )
3019          && ! get_theme_mod( 'header_text', true )
3020      ) {
3021          $classes = (array) get_theme_support( 'custom-logo', 'header-text' );
3022          $classes = array_map( 'sanitize_html_class', $classes );
3023          $classes = '.' . implode( ', .', $classes );
3024          ?>
3025          <!-- Custom Logo: hide header text -->
3026          <style id="custom-logo-css">
3027              <?php echo $classes; ?> {
3028                  position: absolute;
3029                  clip-path: inset(50%);
3030              }
3031          </style>
3032          <?php
3033      }
3034  }
3035  
3036  /**
3037   * Gets the theme support arguments passed when registering that support.
3038   *
3039   * Example usage:
3040   *
3041   *     get_theme_support( 'custom-logo' );
3042   *     get_theme_support( 'custom-header', 'width' );
3043   *
3044   * @since 3.1.0
3045   * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
3046   *              by adding it to the function signature.
3047   *
3048   * @global array $_wp_theme_features
3049   *
3050   * @param string $feature The feature to check. See add_theme_support() for the list
3051   *                        of possible values.
3052   * @param mixed  ...$args Optional extra arguments to be checked against certain features.
3053   * @return mixed The array of extra arguments or the value for the registered feature.
3054   */
3055  function get_theme_support( $feature, ...$args ) {
3056      global $_wp_theme_features;
3057  
3058      if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
3059          return false;
3060      }
3061  
3062      if ( ! $args ) {
3063          return $_wp_theme_features[ $feature ];
3064      }
3065  
3066      switch ( $feature ) {
3067          case 'custom-logo':
3068          case 'custom-header':
3069          case 'custom-background':
3070              return $_wp_theme_features[ $feature ][0][ $args[0] ] ?? false;
3071  
3072          default:
3073              return $_wp_theme_features[ $feature ];
3074      }
3075  }
3076  
3077  /**
3078   * Allows a theme to de-register its support of a certain feature
3079   *
3080   * Should be called in the theme's functions.php file. Generally would
3081   * be used for child themes to override support from the parent theme.
3082   *
3083   * @since 3.0.0
3084   *
3085   * @see add_theme_support()
3086   *
3087   * @param string $feature The feature being removed. See add_theme_support() for the list
3088   *                        of possible values.
3089   * @return bool Whether feature was removed.
3090   */
3091  function remove_theme_support( $feature ) {
3092      /*
3093       * Do not remove internal registrations that are not used directly by themes.
3094       * 'editor-style' (singular) is added by add_editor_style(); use remove_editor_styles().
3095       * It is unrelated to the 'editor-styles' (plural) feature, which can be removed here.
3096       */
3097      if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ), true ) ) {
3098          return false;
3099      }
3100  
3101      return _remove_theme_support( $feature );
3102  }
3103  
3104  /**
3105   * Do not use. Removes theme support internally without knowledge of those not used
3106   * by themes directly.
3107   *
3108   * @access private
3109   * @since 3.1.0
3110   * @global array               $_wp_theme_features
3111   * @global Custom_Image_Header $custom_image_header
3112   * @global Custom_Background   $custom_background
3113   *
3114   * @param string $feature The feature being removed. See add_theme_support() for the list
3115   *                        of possible values.
3116   * @return bool True if support was removed, false if the feature was not registered.
3117   */
3118  function _remove_theme_support( $feature ) {
3119      global $_wp_theme_features;
3120  
3121      switch ( $feature ) {
3122          case 'custom-header-uploads':
3123              if ( ! isset( $_wp_theme_features['custom-header'] ) ) {
3124                  return false;
3125              }
3126              add_theme_support( 'custom-header', array( 'uploads' => false ) );
3127              return true; // Do not continue - custom-header-uploads no longer exists.
3128      }
3129  
3130      if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
3131          return false;
3132      }
3133  
3134      switch ( $feature ) {
3135          case 'custom-header':
3136              if ( ! did_action( 'wp_loaded' ) ) {
3137                  break;
3138              }
3139              $support = get_theme_support( 'custom-header' );
3140              if ( isset( $support[0]['wp-head-callback'] ) ) {
3141                  remove_action( 'wp_head', $support[0]['wp-head-callback'] );
3142              }
3143              if ( isset( $GLOBALS['custom_image_header'] ) ) {
3144                  remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
3145                  unset( $GLOBALS['custom_image_header'] );
3146              }
3147              break;
3148  
3149          case 'custom-background':
3150              if ( ! did_action( 'wp_loaded' ) ) {
3151                  break;
3152              }
3153              $support = get_theme_support( 'custom-background' );
3154              if ( isset( $support[0]['wp-head-callback'] ) ) {
3155                  remove_action( 'wp_head', $support[0]['wp-head-callback'] );
3156              }
3157              remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
3158              unset( $GLOBALS['custom_background'] );
3159              break;
3160      }
3161  
3162      unset( $_wp_theme_features[ $feature ] );
3163  
3164      return true;
3165  }
3166  
3167  /**
3168   * Checks a theme's support for a given feature.
3169   *
3170   * Example usage:
3171   *
3172   *     current_theme_supports( 'custom-logo' );
3173   *     current_theme_supports( 'html5', 'comment-form' );
3174   *
3175   * @since 2.9.0
3176   * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
3177   *              by adding it to the function signature.
3178   *
3179   * @global array $_wp_theme_features
3180   *
3181   * @param string $feature The feature being checked. See add_theme_support() for the list
3182   *                        of possible values.
3183   * @param mixed  ...$args Optional extra arguments to be checked against certain features.
3184   * @return bool True if the active theme supports the feature, false otherwise.
3185   */
3186  function current_theme_supports( $feature, ...$args ) {
3187      global $_wp_theme_features;
3188  
3189      if ( 'custom-header-uploads' === $feature ) {
3190          return current_theme_supports( 'custom-header', 'uploads' );
3191      }
3192  
3193      if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
3194          return false;
3195      }
3196  
3197      // If no args passed then no extra checks need to be performed.
3198      if ( ! $args ) {
3199          /** This filter is documented in wp-includes/theme.php */
3200          return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[ $feature ] ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
3201      }
3202  
3203      switch ( $feature ) {
3204          case 'post-thumbnails':
3205              /*
3206               * post-thumbnails can be registered for only certain content/post types
3207               * by passing an array of types to add_theme_support().
3208               * If no array was passed, then any type is accepted.
3209               */
3210              if ( true === $_wp_theme_features[ $feature ] ) {  // Registered for all types.
3211                  return true;
3212              }
3213              $content_type = $args[0];
3214              return in_array( $content_type, $_wp_theme_features[ $feature ][0], true );
3215  
3216          case 'html5':
3217          case 'post-formats':
3218              /*
3219               * Specific post formats can be registered by passing an array of types
3220               * to add_theme_support().
3221               *
3222               * Specific areas of HTML5 support *must* be passed via an array to add_theme_support().
3223               */
3224              $type = $args[0];
3225              return in_array( $type, $_wp_theme_features[ $feature ][0], true );
3226  
3227          case 'custom-logo':
3228          case 'custom-header':
3229          case 'custom-background':
3230              // Specific capabilities can be registered by passing an array to add_theme_support().
3231              return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
3232      }
3233  
3234      /**
3235       * Filters whether the active theme supports a specific feature.
3236       *
3237       * The dynamic portion of the hook name, `$feature`, refers to the specific
3238       * theme feature. See add_theme_support() for the list of possible values.
3239       *
3240       * @since 3.4.0
3241       *
3242       * @param bool   $supports Whether the active theme supports the given feature. Default true.
3243       * @param array  $args     Array of arguments for the feature.
3244       * @param string $feature  The theme feature.
3245       */
3246      return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[ $feature ] ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
3247  }
3248  
3249  /**
3250   * Checks a theme's support for a given feature before loading the functions which implement it.
3251   *
3252   * @since 2.9.0
3253   *
3254   * @param string $feature The feature being checked. See add_theme_support() for the list
3255   *                        of possible values.
3256   * @param string $file    Path to the file.
3257   * @return bool True if the active theme supports the supplied feature, false otherwise.
3258   */
3259  function require_if_theme_supports( $feature, $file ) {
3260      if ( current_theme_supports( $feature ) ) {
3261          require $file;
3262          return true;
3263      }
3264      return false;
3265  }
3266  
3267  /**
3268   * Registers a theme feature for use in add_theme_support().
3269   *
3270   * This does not indicate that the active theme supports the feature, it only describes
3271   * the feature's supported options.
3272   *
3273   * @since 5.5.0
3274   *
3275   * @see add_theme_support()
3276   *
3277   * @global array $_wp_registered_theme_features
3278   *
3279   * @param string $feature The name uniquely identifying the feature. See add_theme_support()
3280   *                        for the list of possible values.
3281   * @param array  $args {
3282   *     Data used to describe the theme.
3283   *
3284   *     @type string     $type         The type of data associated with this feature.
3285   *                                    Valid values are 'string', 'boolean', 'integer',
3286   *                                    'number', 'array', and 'object'. Defaults to 'boolean'.
3287   *     @type bool       $variadic     Does this feature utilize the variadic support
3288   *                                    of add_theme_support(), or are all arguments specified
3289   *                                    as the second parameter. Must be used with the "array" type.
3290   *     @type string     $description  A short description of the feature. Included in
3291   *                                    the Themes REST API schema. Intended for developers.
3292   *     @type bool|array $show_in_rest {
3293   *         Whether this feature should be included in the Themes REST API endpoint.
3294   *         Defaults to not being included. When registering an 'array' or 'object' type,
3295   *         this argument must be an array with the 'schema' key.
3296   *
3297   *         @type array    $schema           Specifies the JSON Schema definition describing
3298   *                                          the feature. If any objects in the schema do not include
3299   *                                          the 'additionalProperties' keyword, it is set to false.
3300   *         @type string   $name             An alternate name to be used as the property name
3301   *                                          in the REST API.
3302   *         @type callable $prepare_callback A function used to format the theme support in the REST API.
3303   *                                          Receives the raw theme support value.
3304   *      }
3305   * }
3306   * @return true|WP_Error True if the theme feature was successfully registered, a WP_Error object if not.
3307   */
3308  function register_theme_feature( $feature, $args = array() ) {
3309      global $_wp_registered_theme_features;
3310  
3311      if ( ! is_array( $_wp_registered_theme_features ) ) {
3312          $_wp_registered_theme_features = array();
3313      }
3314  
3315      $defaults = array(
3316          'type'         => 'boolean',
3317          'variadic'     => false,
3318          'description'  => '',
3319          'show_in_rest' => false,
3320      );
3321  
3322      $args = wp_parse_args( $args, $defaults );
3323  
3324      if ( true === $args['show_in_rest'] ) {
3325          $args['show_in_rest'] = array();
3326      }
3327  
3328      if ( is_array( $args['show_in_rest'] ) ) {
3329          $args['show_in_rest'] = wp_parse_args(
3330              $args['show_in_rest'],
3331              array(
3332                  'schema'           => array(),
3333                  'name'             => $feature,
3334                  'prepare_callback' => null,
3335              )
3336          );
3337      }
3338  
3339      if ( ! in_array( $args['type'], array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
3340          return new WP_Error(
3341              'invalid_type',
3342              __( 'The feature "type" is not valid JSON Schema type.' )
3343          );
3344      }
3345  
3346      if ( true === $args['variadic'] && 'array' !== $args['type'] ) {
3347          return new WP_Error(
3348              'variadic_must_be_array',
3349              __( 'When registering a "variadic" theme feature, the "type" must be an "array".' )
3350          );
3351      }
3352  
3353      if ( false !== $args['show_in_rest'] && in_array( $args['type'], array( 'array', 'object' ), true ) ) {
3354          if ( ! is_array( $args['show_in_rest'] ) || empty( $args['show_in_rest']['schema'] ) ) {
3355              return new WP_Error(
3356                  'missing_schema',
3357                  __( 'When registering an "array" or "object" feature to show in the REST API, the feature\'s schema must also be defined.' )
3358              );
3359          }
3360  
3361          if ( 'array' === $args['type'] && ! isset( $args['show_in_rest']['schema']['items'] ) ) {
3362              return new WP_Error(
3363                  'missing_schema_items',
3364                  __( 'When registering an "array" feature, the feature\'s schema must include the "items" keyword.' )
3365              );
3366          }
3367  
3368          if ( 'object' === $args['type'] && ! isset( $args['show_in_rest']['schema']['properties'] ) ) {
3369              return new WP_Error(
3370                  'missing_schema_properties',
3371                  __( 'When registering an "object" feature, the feature\'s schema must include the "properties" keyword.' )
3372              );
3373          }
3374      }
3375  
3376      if ( is_array( $args['show_in_rest'] ) ) {
3377          if ( isset( $args['show_in_rest']['prepare_callback'] )
3378              && ! is_callable( $args['show_in_rest']['prepare_callback'] )
3379          ) {
3380              return new WP_Error(
3381                  'invalid_rest_prepare_callback',
3382                  sprintf(
3383                      /* translators: %s: prepare_callback */
3384                      __( 'The "%s" must be a callable function.' ),
3385                      'prepare_callback'
3386                  )
3387              );
3388          }
3389  
3390          $args['show_in_rest']['schema'] = wp_parse_args(
3391              $args['show_in_rest']['schema'],
3392              array(
3393                  'description' => $args['description'],
3394                  'type'        => $args['type'],
3395                  'default'     => false,
3396              )
3397          );
3398  
3399          if ( is_bool( $args['show_in_rest']['schema']['default'] )
3400              && ! in_array( 'boolean', (array) $args['show_in_rest']['schema']['type'], true )
3401          ) {
3402              // Automatically include the "boolean" type when the default value is a boolean.
3403              $args['show_in_rest']['schema']['type'] = (array) $args['show_in_rest']['schema']['type'];
3404              array_unshift( $args['show_in_rest']['schema']['type'], 'boolean' );
3405          }
3406  
3407          $args['show_in_rest']['schema'] = rest_default_additional_properties_to_false( $args['show_in_rest']['schema'] );
3408      }
3409  
3410      $_wp_registered_theme_features[ $feature ] = $args;
3411  
3412      return true;
3413  }
3414  
3415  /**
3416   * Gets the list of registered theme features.
3417   *
3418   * @since 5.5.0
3419   *
3420   * @global array $_wp_registered_theme_features
3421   *
3422   * @return array[] List of theme features, keyed by their name.
3423   */
3424  function get_registered_theme_features() {
3425      global $_wp_registered_theme_features;
3426  
3427      if ( ! is_array( $_wp_registered_theme_features ) ) {
3428          return array();
3429      }
3430  
3431      return $_wp_registered_theme_features;
3432  }
3433  
3434  /**
3435   * Gets the registration config for a theme feature.
3436   *
3437   * @since 5.5.0
3438   *
3439   * @global array $_wp_registered_theme_features
3440   *
3441   * @param string $feature The feature name. See add_theme_support() for the list
3442   *                        of possible values.
3443   * @return array|null The registration args, or null if the feature was not registered.
3444   */
3445  function get_registered_theme_feature( $feature ) {
3446      global $_wp_registered_theme_features;
3447  
3448      if ( ! is_array( $_wp_registered_theme_features ) ) {
3449          return null;
3450      }
3451  
3452      return $_wp_registered_theme_features[ $feature ] ?? null;
3453  }
3454  
3455  /**
3456   * Checks an attachment being deleted to see if it's a header or background image.
3457   *
3458   * If true it removes the theme modification which would be pointing at the deleted
3459   * attachment.
3460   *
3461   * @access private
3462   * @since 3.0.0
3463   * @since 4.3.0 Also removes `header_image_data`.
3464   * @since 4.5.0 Also removes custom logo theme mods.
3465   * @since 6.6.0 Also removes `site_logo` option set by the site logo block.
3466   *
3467   * @param int $id The attachment ID.
3468   */
3469  function _delete_attachment_theme_mod( $id ) {
3470      $attachment_image = wp_get_attachment_url( $id );
3471      $header_image     = get_header_image();
3472      $background_image = get_background_image();
3473      $custom_logo_id   = (int) get_theme_mod( 'custom_logo' );
3474      $site_logo_id     = (int) get_option( 'site_logo' );
3475  
3476      if ( $custom_logo_id && $custom_logo_id === $id ) {
3477          remove_theme_mod( 'custom_logo' );
3478          remove_theme_mod( 'header_text' );
3479      }
3480  
3481      if ( $site_logo_id && $site_logo_id === $id ) {
3482          delete_option( 'site_logo' );
3483      }
3484  
3485      if ( $header_image && $header_image === $attachment_image ) {
3486          remove_theme_mod( 'header_image' );
3487          remove_theme_mod( 'header_image_data' );
3488      }
3489  
3490      if ( $background_image && $background_image === $attachment_image ) {
3491          remove_theme_mod( 'background_image' );
3492      }
3493  }
3494  
3495  /**
3496   * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
3497   *
3498   * See {@see 'after_switch_theme'}.
3499   *
3500   * @since 3.3.0
3501   */
3502  function check_theme_switched() {
3503      $stylesheet = get_option( 'theme_switched' );
3504  
3505      if ( $stylesheet ) {
3506          $old_theme = wp_get_theme( $stylesheet );
3507  
3508          // Prevent widget & menu mapping from running since Customizer already called it up front.
3509          if ( get_option( 'theme_switched_via_customizer' ) ) {
3510              remove_action( 'after_switch_theme', '_wp_menus_changed' );
3511              remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
3512              update_option( 'theme_switched_via_customizer', false );
3513          }
3514  
3515          if ( $old_theme->exists() ) {
3516              /**
3517               * Fires on the next WP load after the theme has been switched.
3518               *
3519               * The parameters differ according to whether the old theme exists or not.
3520               * If the old theme is missing, the old name will instead be the slug
3521               * of the old theme.
3522               *
3523               * See {@see 'switch_theme'}.
3524               *
3525               * @since 3.3.0
3526               *
3527               * @param string   $old_name  Old theme name.
3528               * @param WP_Theme $old_theme WP_Theme instance of the old theme.
3529               */
3530              do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
3531          } else {
3532              /** This action is documented in wp-includes/theme.php */
3533              do_action( 'after_switch_theme', $stylesheet, $old_theme );
3534          }
3535  
3536          flush_rewrite_rules();
3537  
3538          update_option( 'theme_switched', false );
3539      }
3540  }
3541  
3542  /**
3543   * Includes and instantiates the WP_Customize_Manager class.
3544   *
3545   * Loads the Customizer at plugins_loaded when accessing the customize.php admin
3546   * page or when any request includes a wp_customize=on param or a customize_changeset
3547   * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
3548   * WordPress is loading, especially in the Customizer preview
3549   * or when making Customizer Ajax requests for widgets or menus.
3550   *
3551   * @since 3.4.0
3552   *
3553   * @global WP_Customize_Manager $wp_customize
3554   */
3555  function _wp_customize_include() {
3556  
3557      $is_customize_admin_page = ( is_admin() && 'customize.php' === basename( $_SERVER['PHP_SELF'] ) );
3558      $should_include          = (
3559          $is_customize_admin_page
3560          ||
3561          ( isset( $_REQUEST['wp_customize'] ) && 'on' === $_REQUEST['wp_customize'] )
3562          ||
3563          ( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
3564      );
3565  
3566      if ( ! $should_include ) {
3567          return;
3568      }
3569  
3570      /*
3571       * Note that wp_unslash() is not being used on the input vars because it is
3572       * called before wp_magic_quotes() gets called. Besides this fact, none of
3573       * the values should contain any characters needing slashes anyway.
3574       */
3575      $keys       = array(
3576          'changeset_uuid',
3577          'customize_changeset_uuid',
3578          'customize_theme',
3579          'theme',
3580          'customize_messenger_channel',
3581          'customize_autosaved',
3582      );
3583      $input_vars = array_merge(
3584          wp_array_slice_assoc( $_GET, $keys ),
3585          wp_array_slice_assoc( $_POST, $keys )
3586      );
3587  
3588      $theme             = null;
3589      $autosaved         = null;
3590      $messenger_channel = null;
3591  
3592      /*
3593       * Value false indicates UUID should be determined after_setup_theme
3594       * to either re-use existing saved changeset or else generate a new UUID if none exists.
3595       */
3596      $changeset_uuid = false;
3597  
3598      /*
3599       * Set initially to false since defaults to true for back-compat;
3600       * can be overridden via the customize_changeset_branching filter.
3601       */
3602      $branching = false;
3603  
3604      if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
3605          $changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
3606      } elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
3607          $changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
3608      }
3609  
3610      // Note that theme will be sanitized via WP_Theme.
3611      if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
3612          $theme = $input_vars['theme'];
3613      } elseif ( isset( $input_vars['customize_theme'] ) ) {
3614          $theme = $input_vars['customize_theme'];
3615      }
3616  
3617      if ( ! empty( $input_vars['customize_autosaved'] ) ) {
3618          $autosaved = true;
3619      }
3620  
3621      if ( isset( $input_vars['customize_messenger_channel'] ) ) {
3622          $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
3623      }
3624  
3625      /*
3626       * Note that settings must be previewed even outside the customizer preview
3627       * and also in the customizer pane itself. This is to enable loading an existing
3628       * changeset into the customizer. Previewing the settings only has to be prevented
3629       * here in the case of a customize_save action because this will cause WP to think
3630       * there is nothing changed that needs to be saved.
3631       */
3632      $is_customize_save_action = (
3633          wp_doing_ajax()
3634          &&
3635          isset( $_REQUEST['action'] )
3636          &&
3637          'customize_save' === wp_unslash( $_REQUEST['action'] )
3638      );
3639      $settings_previewed       = ! $is_customize_save_action;
3640  
3641      require_once  ABSPATH . WPINC . '/class-wp-customize-manager.php';
3642      $GLOBALS['wp_customize'] = new WP_Customize_Manager(
3643          compact(
3644              'changeset_uuid',
3645              'theme',
3646              'messenger_channel',
3647              'settings_previewed',
3648              'autosaved',
3649              'branching'
3650          )
3651      );
3652  }
3653  
3654  /**
3655   * Publishes a snapshot's changes.
3656   *
3657   * @since 4.7.0
3658   * @access private
3659   *
3660   * @global WP_Customize_Manager $wp_customize Customizer instance.
3661   *
3662   * @param string  $new_status     New post status.
3663   * @param string  $old_status     Old post status.
3664   * @param WP_Post $changeset_post Changeset post object.
3665   */
3666  function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
3667      global $wp_customize;
3668  
3669      $is_publishing_changeset = (
3670          'customize_changeset' === $changeset_post->post_type
3671          &&
3672          'publish' === $new_status
3673          &&
3674          'publish' !== $old_status
3675      );
3676      if ( ! $is_publishing_changeset ) {
3677          return;
3678      }
3679  
3680      if ( empty( $wp_customize ) ) {
3681          require_once  ABSPATH . WPINC . '/class-wp-customize-manager.php';
3682          $wp_customize = new WP_Customize_Manager(
3683              array(
3684                  'changeset_uuid'     => $changeset_post->post_name,
3685                  'settings_previewed' => false,
3686              )
3687          );
3688      }
3689  
3690      if ( ! did_action( 'customize_register' ) ) {
3691          /*
3692           * When running from CLI or Cron, the customize_register action will need
3693           * to be triggered in order for core, themes, and plugins to register their
3694           * settings. Normally core will add_action( 'customize_register' ) at
3695           * priority 10 to register the core settings, and if any themes/plugins
3696           * also add_action( 'customize_register' ) at the same priority, they
3697           * will have a $wp_customize with those settings registered since they
3698           * call add_action() afterward, normally. However, when manually doing
3699           * the customize_register action after the setup_theme, then the order
3700           * will be reversed for two actions added at priority 10, resulting in
3701           * the core settings no longer being available as expected to themes/plugins.
3702           * So the following manually calls the method that registers the core
3703           * settings up front before doing the action.
3704           */
3705          remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
3706          $wp_customize->register_controls();
3707  
3708          /** This filter is documented in wp-includes/class-wp-customize-manager.php */
3709          do_action( 'customize_register', $wp_customize );
3710      }
3711      $wp_customize->_publish_changeset_values( $changeset_post->ID );
3712  
3713      /*
3714       * Trash the changeset post if revisions are not enabled. Unpublished
3715       * changesets by default get garbage collected due to the auto-draft status.
3716       * When a changeset post is published, however, it would no longer get cleaned
3717       * out. This is a problem when the changeset posts are never displayed anywhere,
3718       * since they would just be endlessly piling up. So here we use the revisions
3719       * feature to indicate whether or not a published changeset should get trashed
3720       * and thus garbage collected.
3721       */
3722      if ( ! wp_revisions_enabled( $changeset_post ) ) {
3723          $wp_customize->trash_changeset_post( $changeset_post->ID );
3724      }
3725  }
3726  
3727  /**
3728   * Filters changeset post data upon insert to ensure post_name is intact.
3729   *
3730   * This is needed to prevent the post_name from being dropped when the post is
3731   * transitioned into pending status by a contributor.
3732   *
3733   * @since 4.7.0
3734   *
3735   * @see wp_insert_post()
3736   *
3737   * @param array $post_data          An array of slashed post data.
3738   * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
3739   * @return array Filtered data.
3740   */
3741  function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
3742      if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
3743  
3744          // Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
3745          if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
3746              $post_data['post_name'] = $supplied_post_data['post_name'];
3747          }
3748      }
3749      return $post_data;
3750  }
3751  
3752  /**
3753   * Adds settings for the customize-loader script.
3754   *
3755   * @since 3.4.0
3756   */
3757  function _wp_customize_loader_settings() {
3758      $admin_origin = parse_url( admin_url() );
3759      $home_origin  = parse_url( home_url() );
3760      $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
3761  
3762      $browser = array(
3763          'mobile' => wp_is_mobile(),
3764          'ios'    => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
3765      );
3766  
3767      $settings = array(
3768          'url'           => esc_url( admin_url( 'customize.php' ) ),
3769          'isCrossDomain' => $cross_domain,
3770          'browser'       => $browser,
3771          'l10n'          => array(
3772              'saveAlert'       => __( 'The changes you made will be lost if you navigate away from this page.' ),
3773              'mainIframeTitle' => __( 'Customizer' ),
3774          ),
3775      );
3776  
3777      $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';';
3778  
3779      $wp_scripts = wp_scripts();
3780      $data       = $wp_scripts->get_data( 'customize-loader', 'data' );
3781      if ( $data ) {
3782          $script = "$data\n$script";
3783      }
3784  
3785      $wp_scripts->add_data( 'customize-loader', 'data', $script );
3786  }
3787  
3788  /**
3789   * Returns a URL to load the Customizer.
3790   *
3791   * @since 3.4.0
3792   *
3793   * @param string $stylesheet Optional. Theme to customize. Defaults to active theme.
3794   *                           The theme's stylesheet will be urlencoded if necessary.
3795   * @return string
3796   */
3797  function wp_customize_url( $stylesheet = '' ) {
3798      $url = admin_url( 'customize.php' );
3799      if ( $stylesheet ) {
3800          $url = add_query_arg( 'theme', urlencode( $stylesheet ), $url );
3801      }
3802      return esc_url( $url );
3803  }
3804  
3805  /**
3806   * Prints a script to check whether or not the Customizer is supported,
3807   * and apply either the no-customize-support or customize-support class
3808   * to the body.
3809   *
3810   * This function MUST be called inside the body tag.
3811   *
3812   * Ideally, call this function immediately after the body tag is opened.
3813   * This prevents a flash of unstyled content.
3814   *
3815   * It is also recommended that you add the "no-customize-support" class
3816   * to the body tag by default.
3817   *
3818   * @since 3.4.0
3819   * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
3820   * @since 5.5.0 IE8 and older are no longer supported.
3821   */
3822  function wp_customize_support_script() {
3823      $admin_origin = parse_url( admin_url() );
3824      $home_origin  = parse_url( home_url() );
3825      $cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
3826      ob_start();
3827      ?>
3828      <script>
3829          (function() {
3830              var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
3831  
3832      <?php    if ( $cross_domain ) : ?>
3833              request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
3834      <?php    else : ?>
3835              request = true;
3836      <?php    endif; ?>
3837  
3838              b[c] = b[c].replace( rcs, ' ' );
3839              // The customizer requires postMessage and CORS (if the site is cross domain).
3840              b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
3841          }());
3842      </script>
3843      <?php
3844      wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) . "\n//# sourceURL=" . rawurlencode( __FUNCTION__ ) );
3845  }
3846  
3847  /**
3848   * Whether the site is being previewed in the Customizer.
3849   *
3850   * @since 4.0.0
3851   *
3852   * @global WP_Customize_Manager $wp_customize Customizer instance.
3853   *
3854   * @return bool True if the site is being previewed in the Customizer, false otherwise.
3855   */
3856  function is_customize_preview() {
3857      global $wp_customize;
3858  
3859      return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();
3860  }
3861  
3862  /**
3863   * Makes sure that auto-draft posts get their post_date bumped or status changed
3864   * to draft to prevent premature garbage-collection.
3865   *
3866   * When a changeset is updated but remains an auto-draft, ensure the post_date
3867   * for the auto-draft posts remains the same so that it will be
3868   * garbage-collected at the same time by `wp_delete_auto_drafts()`. Otherwise,
3869   * if the changeset is updated to be a draft then update the posts
3870   * to have a far-future post_date so that they will never be garbage collected
3871   * unless the changeset post itself is deleted.
3872   *
3873   * When a changeset is updated to be a persistent draft or to be scheduled for
3874   * publishing, then transition any dependent auto-drafts to a draft status so
3875   * that they likewise will not be garbage-collected but also so that they can
3876   * be edited in the admin before publishing since there is not yet a post/page
3877   * editing flow in the Customizer. See #39752.
3878   *
3879   * @link https://core.trac.wordpress.org/ticket/39752
3880   *
3881   * @since 4.8.0
3882   * @access private
3883   * @see wp_delete_auto_drafts()
3884   *
3885   * @global wpdb $wpdb WordPress database abstraction object.
3886   *
3887   * @param string   $new_status Transition to this post status.
3888   * @param string   $old_status Previous post status.
3889   * @param \WP_Post $post       Post data.
3890   */
3891  function _wp_keep_alive_customize_changeset_dependent_auto_drafts( $new_status, $old_status, $post ) {
3892      global $wpdb;
3893      unset( $old_status );
3894  
3895      // Short-circuit if not a changeset or if the changeset was published.
3896      if ( 'customize_changeset' !== $post->post_type || 'publish' === $new_status ) {
3897          return;
3898      }
3899  
3900      $data = json_decode( $post->post_content, true );
3901      if ( empty( $data['nav_menus_created_posts']['value'] ) ) {
3902          return;
3903      }
3904  
3905      /*
3906       * Actually, in lieu of keeping alive, trash any customization drafts here if the changeset itself is
3907       * getting trashed. This is needed because when a changeset transitions to a draft, then any of the
3908       * dependent auto-draft post/page stubs will also get transitioned to customization drafts which
3909       * are then visible in the WP Admin. We cannot wait for the deletion of the changeset in which
3910       * _wp_delete_customize_changeset_dependent_auto_drafts() will be called, since they need to be
3911       * trashed to remove from visibility immediately.
3912       */
3913      if ( 'trash' === $new_status ) {
3914          foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
3915              if ( ! empty( $post_id ) && 'draft' === get_post_status( $post_id ) ) {
3916                  wp_trash_post( $post_id );
3917              }
3918          }
3919          return;
3920      }
3921  
3922      $post_args = array();
3923      if ( 'auto-draft' === $new_status ) {
3924          /*
3925           * Keep the post date for the post matching the changeset
3926           * so that it will not be garbage-collected before the changeset.
3927           */
3928          $post_args['post_date'] = $post->post_date; // Note wp_delete_auto_drafts() only looks at this date.
3929      } else {
3930          /*
3931           * Since the changeset no longer has an auto-draft (and it is not published)
3932           * it is now a persistent changeset, a long-lived draft, and so any
3933           * associated auto-draft posts should likewise transition into having a draft
3934           * status. These drafts will be treated differently than regular drafts in
3935           * that they will be tied to the given changeset. The publish meta box is
3936           * replaced with a notice about how the post is part of a set of customized changes
3937           * which will be published when the changeset is published.
3938           */
3939          $post_args['post_status'] = 'draft';
3940      }
3941  
3942      foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
3943          if ( empty( $post_id ) || 'auto-draft' !== get_post_status( $post_id ) ) {
3944              continue;
3945          }
3946          $wpdb->update(
3947              $wpdb->posts,
3948              $post_args,
3949              array( 'ID' => $post_id )
3950          );
3951          clean_post_cache( $post_id );
3952      }
3953  }
3954  
3955  /**
3956   * Creates the initial theme features when the 'setup_theme' action is fired.
3957   *
3958   * See {@see 'setup_theme'}.
3959   *
3960   * @since 5.5.0
3961   * @since 6.0.1 The `block-templates` feature was added.
3962   */
3963  function create_initial_theme_features() {
3964      register_theme_feature(
3965          'align-wide',
3966          array(
3967              'description'  => __( 'Whether theme opts in to wide alignment CSS class.' ),
3968              'show_in_rest' => true,
3969          )
3970      );
3971      register_theme_feature(
3972          'automatic-feed-links',
3973          array(
3974              'description'  => __( 'Whether posts and comments RSS feed links are added to head.' ),
3975              'show_in_rest' => true,
3976          )
3977      );
3978      register_theme_feature(
3979          'block-templates',
3980          array(
3981              'description'  => __( 'Whether a theme uses block-based templates.' ),
3982              'show_in_rest' => true,
3983          )
3984      );
3985      register_theme_feature(
3986          'block-template-parts',
3987          array(
3988              'description'  => __( 'Whether a theme uses block-based template parts.' ),
3989              'show_in_rest' => true,
3990          )
3991      );
3992      register_theme_feature(
3993          'custom-background',
3994          array(
3995              'description'  => __( 'Custom background if defined by the theme.' ),
3996              'type'         => 'object',
3997              'show_in_rest' => array(
3998                  'schema' => array(
3999                      'properties' => array(
4000                          'default-image'      => array(
4001                              'type'   => 'string',
4002                              'format' => 'uri',
4003                          ),
4004                          'default-preset'     => array(
4005                              'type' => 'string',
4006                              'enum' => array(
4007                                  'default',
4008                                  'fill',
4009                                  'fit',
4010                                  'repeat',
4011                                  'custom',
4012                              ),
4013                          ),
4014                          'default-position-x' => array(
4015                              'type' => 'string',
4016                              'enum' => array(
4017                                  'left',
4018                                  'center',
4019                                  'right',
4020                              ),
4021                          ),
4022                          'default-position-y' => array(
4023                              'type' => 'string',
4024                              'enum' => array(
4025                                  'left',
4026                                  'center',
4027                                  'right',
4028                              ),
4029                          ),
4030                          'default-size'       => array(
4031                              'type' => 'string',
4032                              'enum' => array(
4033                                  'auto',
4034                                  'contain',
4035                                  'cover',
4036                              ),
4037                          ),
4038                          'default-repeat'     => array(
4039                              'type' => 'string',
4040                              'enum' => array(
4041                                  'repeat-x',
4042                                  'repeat-y',
4043                                  'repeat',
4044                                  'no-repeat',
4045                              ),
4046                          ),
4047                          'default-attachment' => array(
4048                              'type' => 'string',
4049                              'enum' => array(
4050                                  'scroll',
4051                                  'fixed',
4052                              ),
4053                          ),
4054                          'default-color'      => array(
4055                              'type' => 'string',
4056                          ),
4057                      ),
4058                  ),
4059              ),
4060          )
4061      );
4062      register_theme_feature(
4063          'custom-header',
4064          array(
4065              'description'  => __( 'Custom header if defined by the theme.' ),
4066              'type'         => 'object',
4067              'show_in_rest' => array(
4068                  'schema' => array(
4069                      'properties' => array(
4070                          'default-image'      => array(
4071                              'type'   => 'string',
4072                              'format' => 'uri',
4073                          ),
4074                          'random-default'     => array(
4075                              'type' => 'boolean',
4076                          ),
4077                          'width'              => array(
4078                              'type' => 'integer',
4079                          ),
4080                          'height'             => array(
4081                              'type' => 'integer',
4082                          ),
4083                          'flex-height'        => array(
4084                              'type' => 'boolean',
4085                          ),
4086                          'flex-width'         => array(
4087                              'type' => 'boolean',
4088                          ),
4089                          'default-text-color' => array(
4090                              'type' => 'string',
4091                          ),
4092                          'header-text'        => array(
4093                              'type' => 'boolean',
4094                          ),
4095                          'uploads'            => array(
4096                              'type' => 'boolean',
4097                          ),
4098                          'video'              => array(
4099                              'type' => 'boolean',
4100                          ),
4101                      ),
4102                  ),
4103              ),
4104          )
4105      );
4106      register_theme_feature(
4107          'custom-logo',
4108          array(
4109              'type'         => 'object',
4110              'description'  => __( 'Custom logo if defined by the theme.' ),
4111              'show_in_rest' => array(
4112                  'schema' => array(
4113                      'properties' => array(
4114                          'width'                => array(
4115                              'type' => 'integer',
4116                          ),
4117                          'height'               => array(
4118                              'type' => 'integer',
4119                          ),
4120                          'flex-width'           => array(
4121                              'type' => 'boolean',
4122                          ),
4123                          'flex-height'          => array(
4124                              'type' => 'boolean',
4125                          ),
4126                          'header-text'          => array(
4127                              'type'  => 'array',
4128                              'items' => array(
4129                                  'type' => 'string',
4130                              ),
4131                          ),
4132                          'unlink-homepage-logo' => array(
4133                              'type' => 'boolean',
4134                          ),
4135                      ),
4136                  ),
4137              ),
4138          )
4139      );
4140      register_theme_feature(
4141          'customize-selective-refresh-widgets',
4142          array(
4143              'description'  => __( 'Whether the theme enables Selective Refresh for Widgets being managed with the Customizer.' ),
4144              'show_in_rest' => true,
4145          )
4146      );
4147      register_theme_feature(
4148          'dark-editor-style',
4149          array(
4150              'description'  => __( 'Whether theme opts in to the dark editor style UI.' ),
4151              'show_in_rest' => true,
4152          )
4153      );
4154      register_theme_feature(
4155          'disable-custom-colors',
4156          array(
4157              'description'  => __( 'Whether the theme disables custom colors.' ),
4158              'show_in_rest' => true,
4159          )
4160      );
4161      register_theme_feature(
4162          'disable-custom-font-sizes',
4163          array(
4164              'description'  => __( 'Whether the theme disables custom font sizes.' ),
4165              'show_in_rest' => true,
4166          )
4167      );
4168      register_theme_feature(
4169          'disable-custom-gradients',
4170          array(
4171              'description'  => __( 'Whether the theme disables custom gradients.' ),
4172              'show_in_rest' => true,
4173          )
4174      );
4175      register_theme_feature(
4176          'disable-layout-styles',
4177          array(
4178              'description'  => __( 'Whether the theme disables generated layout styles.' ),
4179              'show_in_rest' => true,
4180          )
4181      );
4182      register_theme_feature(
4183          'editor-color-palette',
4184          array(
4185              'type'         => 'array',
4186              'description'  => __( 'Custom color palette if defined by the theme.' ),
4187              'show_in_rest' => array(
4188                  'schema' => array(
4189                      'items' => array(
4190                          'type'       => 'object',
4191                          'properties' => array(
4192                              'name'  => array(
4193                                  'type' => 'string',
4194                              ),
4195                              'slug'  => array(
4196                                  'type' => 'string',
4197                              ),
4198                              'color' => array(
4199                                  'type' => 'string',
4200                              ),
4201                          ),
4202                      ),
4203                  ),
4204              ),
4205          )
4206      );
4207      register_theme_feature(
4208          'editor-font-sizes',
4209          array(
4210              'type'         => 'array',
4211              'description'  => __( 'Custom font sizes if defined by the theme.' ),
4212              'show_in_rest' => array(
4213                  'schema' => array(
4214                      'items' => array(
4215                          'type'       => 'object',
4216                          'properties' => array(
4217                              'name' => array(
4218                                  'type' => 'string',
4219                              ),
4220                              'size' => array(
4221                                  'type' => 'number',
4222                              ),
4223                              'slug' => array(
4224                                  'type' => 'string',
4225                              ),
4226                          ),
4227                      ),
4228                  ),
4229              ),
4230          )
4231      );
4232      register_theme_feature(
4233          'editor-gradient-presets',
4234          array(
4235              'type'         => 'array',
4236              'description'  => __( 'Custom gradient presets if defined by the theme.' ),
4237              'show_in_rest' => array(
4238                  'schema' => array(
4239                      'items' => array(
4240                          'type'       => 'object',
4241                          'properties' => array(
4242                              'name'     => array(
4243                                  'type' => 'string',
4244                              ),
4245                              'gradient' => array(
4246                                  'type' => 'string',
4247                              ),
4248                              'slug'     => array(
4249                                  'type' => 'string',
4250                              ),
4251                          ),
4252                      ),
4253                  ),
4254              ),
4255          )
4256      );
4257      register_theme_feature(
4258          'editor-spacing-sizes',
4259          array(
4260              'type'         => 'array',
4261              'description'  => __( 'Custom spacing sizes if defined by the theme.' ),
4262              'show_in_rest' => array(
4263                  'schema' => array(
4264                      'items' => array(
4265                          'type'       => 'object',
4266                          'properties' => array(
4267                              'name' => array(
4268                                  'type' => 'string',
4269                              ),
4270                              'size' => array(
4271                                  'type' => 'string',
4272                              ),
4273                              'slug' => array(
4274                                  'type' => 'string',
4275                              ),
4276                          ),
4277                      ),
4278                  ),
4279              ),
4280          )
4281      );
4282      register_theme_feature(
4283          'editor-styles',
4284          array(
4285              'description'  => __( 'Whether theme opts in to the editor styles CSS wrapper.' ),
4286              'show_in_rest' => true,
4287          )
4288      );
4289      register_theme_feature(
4290          'html5',
4291          array(
4292              'type'         => 'array',
4293              'description'  => __( 'Allows use of HTML5 markup for search forms, comment forms, comment lists, gallery, and caption.' ),
4294              'show_in_rest' => array(
4295                  'schema' => array(
4296                      'items' => array(
4297                          'type' => 'string',
4298                          'enum' => array(
4299                              'search-form',
4300                              'comment-form',
4301                              'comment-list',
4302                              'gallery',
4303                              'caption',
4304                              'script',
4305                              'style',
4306                          ),
4307                      ),
4308                  ),
4309              ),
4310          )
4311      );
4312      register_theme_feature(
4313          'post-formats',
4314          array(
4315              'type'         => 'array',
4316              'description'  => __( 'Post formats supported.' ),
4317              'show_in_rest' => array(
4318                  'name'             => 'formats',
4319                  'schema'           => array(
4320                      'items'   => array(
4321                          'type' => 'string',
4322                          'enum' => get_post_format_slugs(),
4323                      ),
4324                      'default' => array( 'standard' ),
4325                  ),
4326                  'prepare_callback' => static function ( $formats ) {
4327                      $formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
4328                      $formats = array_merge( array( 'standard' ), $formats );
4329  
4330                      return $formats;
4331                  },
4332              ),
4333          )
4334      );
4335      register_theme_feature(
4336          'post-thumbnails',
4337          array(
4338              'type'         => 'array',
4339              'description'  => __( 'The post types that support thumbnails or true if all post types are supported.' ),
4340              'show_in_rest' => array(
4341                  'type'   => array( 'boolean', 'array' ),
4342                  'schema' => array(
4343                      'items' => array(
4344                          'type' => 'string',
4345                      ),
4346                  ),
4347              ),
4348          )
4349      );
4350      register_theme_feature(
4351          'responsive-embeds',
4352          array(
4353              'description'  => __( 'Whether the theme supports responsive embedded content.' ),
4354              'show_in_rest' => true,
4355          )
4356      );
4357      register_theme_feature(
4358          'title-tag',
4359          array(
4360              'description'  => __( 'Whether the theme can manage the document title tag.' ),
4361              'show_in_rest' => true,
4362          )
4363      );
4364      register_theme_feature(
4365          'wp-block-styles',
4366          array(
4367              'description'  => __( 'Whether theme opts in to default WordPress block styles for viewing.' ),
4368              'show_in_rest' => true,
4369          )
4370      );
4371  }
4372  
4373  /**
4374   * Returns whether the active theme is a block-based theme or not.
4375   *
4376   * @since 5.9.0
4377   *
4378   * @global string[] $wp_theme_directories
4379   *
4380   * @return bool Whether the active theme is a block-based theme or not.
4381   */
4382  function wp_is_block_theme() {
4383      if ( empty( $GLOBALS['wp_theme_directories'] ) ) {
4384          _doing_it_wrong( __FUNCTION__, __( 'This function should not be called before the theme directory is registered.' ), '6.8.0' );
4385          return false;
4386      }
4387  
4388      return wp_get_theme()->is_block_theme();
4389  }
4390  
4391  /**
4392   * Given an element name, returns a class name.
4393   *
4394   * Alias of WP_Theme_JSON::get_element_class_name.
4395   *
4396   * @since 6.1.0
4397   *
4398   * @param string $element The name of the element.
4399   * @return string The name of the class.
4400   */
4401  function wp_theme_get_element_class_name( $element ) {
4402      return WP_Theme_JSON::get_element_class_name( $element );
4403  }
4404  
4405  /**
4406   * Adds default theme supports for block themes when the 'after_setup_theme' action fires.
4407   *
4408   * See {@see 'after_setup_theme'}.
4409   *
4410   * @since 5.9.0
4411   * @access private
4412   */
4413  function _add_default_theme_supports() {
4414      if ( ! wp_is_block_theme() ) {
4415          return;
4416      }
4417  
4418      add_theme_support( 'post-thumbnails' );
4419      add_theme_support( 'responsive-embeds' );
4420      add_theme_support( 'editor-styles' );
4421      /*
4422       * Makes block themes support HTML5 by default for the comment block and search form
4423       * (which use default template functions) and `[caption]` and `[gallery]` shortcodes.
4424       * Other blocks contain their own HTML5 markup.
4425       */
4426      add_theme_support( 'html5', array( 'comment-form', 'comment-list', 'search-form', 'gallery', 'caption', 'style', 'script' ) );
4427      add_theme_support( 'automatic-feed-links' );
4428  
4429      add_filter( 'should_load_separate_core_block_assets', '__return_true' );
4430      add_filter( 'should_load_block_assets_on_demand', '__return_true' );
4431  
4432      /*
4433       * Remove the Customizer's Menus panel when block theme is active.
4434       */
4435      add_filter(
4436          'customize_panel_active',
4437          static function ( $active, WP_Customize_Panel $panel ) {
4438              if (
4439                  'nav_menus' === $panel->id &&
4440                  ! current_theme_supports( 'menus' ) &&
4441                  ! current_theme_supports( 'widgets' )
4442              ) {
4443                  $active = false;
4444              }
4445              return $active;
4446          },
4447          10,
4448          2
4449      );
4450  }


Generated : Sun Oct 25 08:20:35 2026 Cross-referenced by PHPXref