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


Generated : Fri Sep 30 08:20:01 2022 Cross-referenced by PHPXref