[ Index ]

PHP Cross Reference of WordPress

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 current 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 current 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 current 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 current theme.
 186   *
 187   * @since 1.5.0
 188   *
 189   * @return string Path to current 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 current theme.
 198       *
 199       * @since 1.5.0
 200       *
 201       * @param string $stylesheet_dir Absolute path to the current theme.
 202       * @param string $stylesheet     Directory name of the current 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 current theme.
 210   *
 211   * @since 1.5.0
 212   *
 213   * @return string URI to current 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 current 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 current 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 current theme stylesheet.
 247       *
 248       * @since 1.5.0
 249       *
 250       * @param string $stylesheet_uri     Stylesheet URI for the current theme/child theme.
 251       * @param string $stylesheet_dir_uri Stylesheet directory URI for the current 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 current 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 current 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 current theme.
 310       *
 311       * @since 1.5.0
 312       *
 313       * @param string $template Current theme's directory name.
 314       */
 315      return apply_filters( 'template', get_option( 'template' ) );
 316  }
 317  
 318  /**
 319   * Retrieves template directory path for current theme.
 320   *
 321   * @since 1.5.0
 322   *
 323   * @return string Path to current 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 current theme directory path.
 332       *
 333       * @since 1.5.0
 334       *
 335       * @param string $template_dir The path of the current theme directory.
 336       * @param string $template     Directory name of the current 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 current theme.
 344   *
 345   * @since 1.5.0
 346   *
 347   * @return string URI to current 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 current theme directory URI.
 356       *
 357       * @since 1.5.0
 358       *
 359       * @param string $template_dir_uri The URI of the current theme directory.
 360       * @param string $template         Directory name of the current 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 current 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 current 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 current theme has 'index.php' and 'style.css' files.
 834   *
 835   * Does not initially check the default theme, which is the fallback and should always exist.
 836   * But if it doesn't exist, it'll fall back to the latest core default theme that does exist.
 837   * Will switch theme to the fallback theme if current theme does not validate.
 838   *
 839   * You can use the {@see 'validate_current_theme'} filter to return false to disable
 840   * this functionality.
 841   *
 842   * @since 1.5.0
 843   *
 844   * @see WP_DEFAULT_THEME
 845   *
 846   * @return bool
 847   */
 848  function validate_current_theme() {
 849      /**
 850       * Filters whether to validate the current theme.
 851       *
 852       * @since 2.7.0
 853       *
 854       * @param bool $validate Whether to validate the current theme. Default true.
 855       */
 856      if ( wp_installing() || ! apply_filters( 'validate_current_theme', true ) ) {
 857          return true;
 858      }
 859  
 860      if ( ! file_exists( get_template_directory() . '/index.php' ) ) {
 861          // Invalid.
 862      } elseif ( ! file_exists( get_template_directory() . '/style.css' ) ) {
 863          // Invalid.
 864      } elseif ( is_child_theme() && ! file_exists( get_stylesheet_directory() . '/style.css' ) ) {
 865          // Invalid.
 866      } else {
 867          // Valid.
 868          return true;
 869      }
 870  
 871      $default = wp_get_theme( WP_DEFAULT_THEME );
 872      if ( $default->exists() ) {
 873          switch_theme( WP_DEFAULT_THEME );
 874          return false;
 875      }
 876  
 877      /**
 878       * If we're in an invalid state but WP_DEFAULT_THEME doesn't exist,
 879       * switch to the latest core default theme that's installed.
 880       *
 881       * If it turns out that this latest core default theme is our current
 882       * theme, then there's nothing we can do about that, so we have to bail,
 883       * rather than going into an infinite loop. (This is why there are
 884       * checks against WP_DEFAULT_THEME above, also.) We also can't do anything
 885       * if it turns out there is no default theme installed. (That's `false`.)
 886       */
 887      $default = WP_Theme::get_core_default_theme();
 888      if ( false === $default || get_stylesheet() == $default->get_stylesheet() ) {
 889          return true;
 890      }
 891  
 892      switch_theme( $default->get_stylesheet() );
 893      return false;
 894  }
 895  
 896  /**
 897   * Validates the theme requirements for WordPress version and PHP version.
 898   *
 899   * Uses the information from `Requires at least` and `Requires PHP` headers
 900   * defined in the theme's `style.css` file.
 901   *
 902   * If the headers are not present in the theme's stylesheet file,
 903   * `readme.txt` is also checked as a fallback.
 904   *
 905   * @since 5.5.0
 906   *
 907   * @param string $stylesheet Directory name for the theme.
 908   * @return true|WP_Error True if requirements are met, WP_Error on failure.
 909   */
 910  function validate_theme_requirements( $stylesheet ) {
 911      $theme = wp_get_theme( $stylesheet );
 912  
 913      $requirements = array(
 914          'requires'     => ! empty( $theme->get( 'RequiresWP' ) ) ? $theme->get( 'RequiresWP' ) : '',
 915          'requires_php' => ! empty( $theme->get( 'RequiresPHP' ) ) ? $theme->get( 'RequiresPHP' ) : '',
 916      );
 917  
 918      $readme_file = $theme->theme_root . '/' . $stylesheet . '/readme.txt';
 919  
 920      if ( file_exists( $readme_file ) ) {
 921          $readme_headers = get_file_data(
 922              $readme_file,
 923              array(
 924                  'requires'     => 'Requires at least',
 925                  'requires_php' => 'Requires PHP',
 926              ),
 927              'theme'
 928          );
 929  
 930          $requirements = array_merge( $readme_headers, $requirements );
 931      }
 932  
 933      $compatible_wp  = is_wp_version_compatible( $requirements['requires'] );
 934      $compatible_php = is_php_version_compatible( $requirements['requires_php'] );
 935  
 936      if ( ! $compatible_wp && ! $compatible_php ) {
 937          return new WP_Error(
 938              'theme_wp_php_incompatible',
 939              sprintf(
 940                  /* translators: %s: Theme name. */
 941                  _x( '<strong>Error:</strong> Current WordPress and PHP versions do not meet minimum requirements for %s.', 'theme' ),
 942                  $theme->display( 'Name' )
 943              )
 944          );
 945      } elseif ( ! $compatible_php ) {
 946          return new WP_Error(
 947              'theme_php_incompatible',
 948              sprintf(
 949                  /* translators: %s: Theme name. */
 950                  _x( '<strong>Error:</strong> Current PHP version does not meet minimum requirements for %s.', 'theme' ),
 951                  $theme->display( 'Name' )
 952              )
 953          );
 954      } elseif ( ! $compatible_wp ) {
 955          return new WP_Error(
 956              'theme_wp_incompatible',
 957              sprintf(
 958                  /* translators: %s: Theme name. */
 959                  _x( '<strong>Error:</strong> Current WordPress version does not meet minimum requirements for %s.', 'theme' ),
 960                  $theme->display( 'Name' )
 961              )
 962          );
 963      }
 964  
 965      return true;
 966  }
 967  
 968  /**
 969   * Retrieves all theme modifications.
 970   *
 971   * @since 3.1.0
 972   *
 973   * @return array|void Theme modifications.
 974   */
 975  function get_theme_mods() {
 976      $theme_slug = get_option( 'stylesheet' );
 977      $mods       = get_option( "theme_mods_$theme_slug" );
 978      if ( false === $mods ) {
 979          $theme_name = get_option( 'current_theme' );
 980          if ( false === $theme_name ) {
 981              $theme_name = wp_get_theme()->get( 'Name' );
 982          }
 983          $mods = get_option( "mods_$theme_name" ); // Deprecated location.
 984          if ( is_admin() && false !== $mods ) {
 985              update_option( "theme_mods_$theme_slug", $mods );
 986              delete_option( "mods_$theme_name" );
 987          }
 988      }
 989      return $mods;
 990  }
 991  
 992  /**
 993   * Retrieves theme modification value for the current theme.
 994   *
 995   * If the modification name does not exist, then the $default will be passed
 996   * through {@link https://www.php.net/sprintf sprintf()} PHP function with
 997   * the template directory URI as the first string and the stylesheet directory URI
 998   * as the second string.
 999   *
1000   * @since 2.1.0
1001   *
1002   * @param string       $name    Theme modification name.
1003   * @param string|false $default Optional. Theme modification default value. Default false.
1004   * @return mixed Theme modification value.
1005   */
1006  function get_theme_mod( $name, $default = false ) {
1007      $mods = get_theme_mods();
1008  
1009      if ( isset( $mods[ $name ] ) ) {
1010          /**
1011           * Filters the theme modification, or 'theme_mod', value.
1012           *
1013           * The dynamic portion of the hook name, `$name`, refers to the key name
1014           * of the modification array. For example, 'header_textcolor', 'header_image',
1015           * and so on depending on the theme options.
1016           *
1017           * @since 2.2.0
1018           *
1019           * @param string $current_mod The value of the current theme modification.
1020           */
1021          return apply_filters( "theme_mod_{$name}", $mods[ $name ] );
1022      }
1023  
1024      if ( is_string( $default ) ) {
1025          // Only run the replacement if an sprintf() string format pattern was found.
1026          if ( preg_match( '#(?<!%)%(?:\d+\$?)?s#', $default ) ) {
1027              // Remove a single trailing percent sign.
1028              $default = preg_replace( '#(?<!%)%$#', '', $default );
1029              $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
1030          }
1031      }
1032  
1033      /** This filter is documented in wp-includes/theme.php */
1034      return apply_filters( "theme_mod_{$name}", $default );
1035  }
1036  
1037  /**
1038   * Updates theme modification value for the current theme.
1039   *
1040   * @since 2.1.0
1041   * @since 5.6.0 A return value was added.
1042   *
1043   * @param string $name  Theme modification name.
1044   * @param mixed  $value Theme modification value.
1045   * @return bool True if the value was updated, false otherwise.
1046   */
1047  function set_theme_mod( $name, $value ) {
1048      $mods      = get_theme_mods();
1049      $old_value = isset( $mods[ $name ] ) ? $mods[ $name ] : false;
1050  
1051      /**
1052       * Filters the theme modification, or 'theme_mod', value on save.
1053       *
1054       * The dynamic portion of the hook name, `$name`, refers to the key name
1055       * of the modification array. For example, 'header_textcolor', 'header_image',
1056       * and so on depending on the theme options.
1057       *
1058       * @since 3.9.0
1059       *
1060       * @param string $value     The new value of the theme modification.
1061       * @param string $old_value The current value of the theme modification.
1062       */
1063      $mods[ $name ] = apply_filters( "pre_set_theme_mod_{$name}", $value, $old_value );
1064  
1065      $theme = get_option( 'stylesheet' );
1066  
1067      return update_option( "theme_mods_$theme", $mods );
1068  }
1069  
1070  /**
1071   * Removes theme modification name from current theme list.
1072   *
1073   * If removing the name also removes all elements, then the entire option
1074   * will be removed.
1075   *
1076   * @since 2.1.0
1077   *
1078   * @param string $name Theme modification name.
1079   */
1080  function remove_theme_mod( $name ) {
1081      $mods = get_theme_mods();
1082  
1083      if ( ! isset( $mods[ $name ] ) ) {
1084          return;
1085      }
1086  
1087      unset( $mods[ $name ] );
1088  
1089      if ( empty( $mods ) ) {
1090          remove_theme_mods();
1091          return;
1092      }
1093  
1094      $theme = get_option( 'stylesheet' );
1095  
1096      update_option( "theme_mods_$theme", $mods );
1097  }
1098  
1099  /**
1100   * Removes theme modifications option for current theme.
1101   *
1102   * @since 2.1.0
1103   */
1104  function remove_theme_mods() {
1105      delete_option( 'theme_mods_' . get_option( 'stylesheet' ) );
1106  
1107      // Old style.
1108      $theme_name = get_option( 'current_theme' );
1109      if ( false === $theme_name ) {
1110          $theme_name = wp_get_theme()->get( 'Name' );
1111      }
1112  
1113      delete_option( 'mods_' . $theme_name );
1114  }
1115  
1116  /**
1117   * Retrieves the custom header text color in 3- or 6-digit hexadecimal form.
1118   *
1119   * @since 2.1.0
1120   *
1121   * @return string Header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
1122   */
1123  function get_header_textcolor() {
1124      return get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
1125  }
1126  
1127  /**
1128   * Displays the custom header text color in 3- or 6-digit hexadecimal form (minus the hash symbol).
1129   *
1130   * @since 2.1.0
1131   */
1132  function header_textcolor() {
1133      echo get_header_textcolor();
1134  }
1135  
1136  /**
1137   * Whether to display the header text.
1138   *
1139   * @since 3.4.0
1140   *
1141   * @return bool
1142   */
1143  function display_header_text() {
1144      if ( ! current_theme_supports( 'custom-header', 'header-text' ) ) {
1145          return false;
1146      }
1147  
1148      $text_color = get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
1149      return 'blank' !== $text_color;
1150  }
1151  
1152  /**
1153   * Checks whether a header image is set or not.
1154   *
1155   * @since 4.2.0
1156   *
1157   * @see get_header_image()
1158   *
1159   * @return bool Whether a header image is set or not.
1160   */
1161  function has_header_image() {
1162      return (bool) get_header_image();
1163  }
1164  
1165  /**
1166   * Retrieves header image for custom header.
1167   *
1168   * @since 2.1.0
1169   *
1170   * @return string|false
1171   */
1172  function get_header_image() {
1173      $url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1174  
1175      if ( 'remove-header' === $url ) {
1176          return false;
1177      }
1178  
1179      if ( is_random_header_image() ) {
1180          $url = get_random_header_image();
1181      }
1182  
1183      return esc_url_raw( set_url_scheme( $url ) );
1184  }
1185  
1186  /**
1187   * Creates image tag markup for a custom header image.
1188   *
1189   * @since 4.4.0
1190   *
1191   * @param array $attr Optional. Additional attributes for the image tag. Can be used
1192   *                              to override the default attributes. Default empty.
1193   * @return string HTML image element markup or empty string on failure.
1194   */
1195  function get_header_image_tag( $attr = array() ) {
1196      $header      = get_custom_header();
1197      $header->url = get_header_image();
1198  
1199      if ( ! $header->url ) {
1200          return '';
1201      }
1202  
1203      $width  = absint( $header->width );
1204      $height = absint( $header->height );
1205  
1206      $attr = wp_parse_args(
1207          $attr,
1208          array(
1209              'src'    => $header->url,
1210              'width'  => $width,
1211              'height' => $height,
1212              'alt'    => get_bloginfo( 'name' ),
1213          )
1214      );
1215  
1216      // Generate 'srcset' and 'sizes' if not already present.
1217      if ( empty( $attr['srcset'] ) && ! empty( $header->attachment_id ) ) {
1218          $image_meta = get_post_meta( $header->attachment_id, '_wp_attachment_metadata', true );
1219          $size_array = array( $width, $height );
1220  
1221          if ( is_array( $image_meta ) ) {
1222              $srcset = wp_calculate_image_srcset( $size_array, $header->url, $image_meta, $header->attachment_id );
1223              $sizes  = ! empty( $attr['sizes'] ) ? $attr['sizes'] : wp_calculate_image_sizes( $size_array, $header->url, $image_meta, $header->attachment_id );
1224  
1225              if ( $srcset && $sizes ) {
1226                  $attr['srcset'] = $srcset;
1227                  $attr['sizes']  = $sizes;
1228              }
1229          }
1230      }
1231  
1232      $attr = array_map( 'esc_attr', $attr );
1233      $html = '<img';
1234  
1235      foreach ( $attr as $name => $value ) {
1236          $html .= ' ' . $name . '="' . $value . '"';
1237      }
1238  
1239      $html .= ' />';
1240  
1241      /**
1242       * Filters the markup of header images.
1243       *
1244       * @since 4.4.0
1245       *
1246       * @param string $html   The HTML image tag markup being filtered.
1247       * @param object $header The custom header object returned by 'get_custom_header()'.
1248       * @param array  $attr   Array of the attributes for the image tag.
1249       */
1250      return apply_filters( 'get_header_image_tag', $html, $header, $attr );
1251  }
1252  
1253  /**
1254   * Displays the image markup for a custom header image.
1255   *
1256   * @since 4.4.0
1257   *
1258   * @param array $attr Optional. Attributes for the image markup. Default empty.
1259   */
1260  function the_header_image_tag( $attr = array() ) {
1261      echo get_header_image_tag( $attr );
1262  }
1263  
1264  /**
1265   * Gets random header image data from registered images in theme.
1266   *
1267   * @since 3.4.0
1268   *
1269   * @access private
1270   *
1271   * @global array $_wp_default_headers
1272   *
1273   * @return object
1274   */
1275  function _get_random_header_data() {
1276      static $_wp_random_header = null;
1277  
1278      if ( empty( $_wp_random_header ) ) {
1279          global $_wp_default_headers;
1280          $header_image_mod = get_theme_mod( 'header_image', '' );
1281          $headers          = array();
1282  
1283          if ( 'random-uploaded-image' === $header_image_mod ) {
1284              $headers = get_uploaded_header_images();
1285          } elseif ( ! empty( $_wp_default_headers ) ) {
1286              if ( 'random-default-image' === $header_image_mod ) {
1287                  $headers = $_wp_default_headers;
1288              } else {
1289                  if ( current_theme_supports( 'custom-header', 'random-default' ) ) {
1290                      $headers = $_wp_default_headers;
1291                  }
1292              }
1293          }
1294  
1295          if ( empty( $headers ) ) {
1296              return new stdClass;
1297          }
1298  
1299          $_wp_random_header = (object) $headers[ array_rand( $headers ) ];
1300  
1301          $_wp_random_header->url           = sprintf( $_wp_random_header->url, get_template_directory_uri(), get_stylesheet_directory_uri() );
1302          $_wp_random_header->thumbnail_url = sprintf( $_wp_random_header->thumbnail_url, get_template_directory_uri(), get_stylesheet_directory_uri() );
1303      }
1304  
1305      return $_wp_random_header;
1306  }
1307  
1308  /**
1309   * Gets random header image URL from registered images in theme.
1310   *
1311   * @since 3.2.0
1312   *
1313   * @return string Path to header image.
1314   */
1315  function get_random_header_image() {
1316      $random_image = _get_random_header_data();
1317  
1318      if ( empty( $random_image->url ) ) {
1319          return '';
1320      }
1321  
1322      return $random_image->url;
1323  }
1324  
1325  /**
1326   * Checks if random header image is in use.
1327   *
1328   * Always true if user expressly chooses the option in Appearance > Header.
1329   * Also true if theme has multiple header images registered, no specific header image
1330   * is chosen, and theme turns on random headers with add_theme_support().
1331   *
1332   * @since 3.2.0
1333   *
1334   * @param string $type The random pool to use. Possible values include 'any',
1335   *                     'default', 'uploaded'. Default 'any'.
1336   * @return bool
1337   */
1338  function is_random_header_image( $type = 'any' ) {
1339      $header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1340  
1341      if ( 'any' === $type ) {
1342          if ( 'random-default-image' === $header_image_mod
1343              || 'random-uploaded-image' === $header_image_mod
1344              || ( '' !== get_random_header_image() && empty( $header_image_mod ) )
1345          ) {
1346              return true;
1347          }
1348      } else {
1349          if ( "random-$type-image" === $header_image_mod ) {
1350              return true;
1351          } elseif ( 'default' === $type && empty( $header_image_mod ) && '' !== get_random_header_image() ) {
1352              return true;
1353          }
1354      }
1355  
1356      return false;
1357  }
1358  
1359  /**
1360   * Displays header image URL.
1361   *
1362   * @since 2.1.0
1363   */
1364  function header_image() {
1365      $image = get_header_image();
1366  
1367      if ( $image ) {
1368          echo esc_url( $image );
1369      }
1370  }
1371  
1372  /**
1373   * Gets the header images uploaded for the current theme.
1374   *
1375   * @since 3.2.0
1376   *
1377   * @return array
1378   */
1379  function get_uploaded_header_images() {
1380      $header_images = array();
1381  
1382      // @todo Caching.
1383      $headers = get_posts(
1384          array(
1385              'post_type'  => 'attachment',
1386              'meta_key'   => '_wp_attachment_is_custom_header',
1387              'meta_value' => get_option( 'stylesheet' ),
1388              'orderby'    => 'none',
1389              'nopaging'   => true,
1390          )
1391      );
1392  
1393      if ( empty( $headers ) ) {
1394          return array();
1395      }
1396  
1397      foreach ( (array) $headers as $header ) {
1398          $url          = esc_url_raw( wp_get_attachment_url( $header->ID ) );
1399          $header_data  = wp_get_attachment_metadata( $header->ID );
1400          $header_index = $header->ID;
1401  
1402          $header_images[ $header_index ]                      = array();
1403          $header_images[ $header_index ]['attachment_id']     = $header->ID;
1404          $header_images[ $header_index ]['url']               = $url;
1405          $header_images[ $header_index ]['thumbnail_url']     = $url;
1406          $header_images[ $header_index ]['alt_text']          = get_post_meta( $header->ID, '_wp_attachment_image_alt', true );
1407          $header_images[ $header_index ]['attachment_parent'] = isset( $header_data['attachment_parent'] ) ? $header_data['attachment_parent'] : '';
1408  
1409          if ( isset( $header_data['width'] ) ) {
1410              $header_images[ $header_index ]['width'] = $header_data['width'];
1411          }
1412          if ( isset( $header_data['height'] ) ) {
1413              $header_images[ $header_index ]['height'] = $header_data['height'];
1414          }
1415      }
1416  
1417      return $header_images;
1418  }
1419  
1420  /**
1421   * Gets the header image data.
1422   *
1423   * @since 3.4.0
1424   *
1425   * @global array $_wp_default_headers
1426   *
1427   * @return object
1428   */
1429  function get_custom_header() {
1430      global $_wp_default_headers;
1431  
1432      if ( is_random_header_image() ) {
1433          $data = _get_random_header_data();
1434      } else {
1435          $data = get_theme_mod( 'header_image_data' );
1436          if ( ! $data && current_theme_supports( 'custom-header', 'default-image' ) ) {
1437              $directory_args        = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
1438              $data                  = array();
1439              $data['url']           = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
1440              $data['thumbnail_url'] = $data['url'];
1441              if ( ! empty( $_wp_default_headers ) ) {
1442                  foreach ( (array) $_wp_default_headers as $default_header ) {
1443                      $url = vsprintf( $default_header['url'], $directory_args );
1444                      if ( $data['url'] == $url ) {
1445                          $data                  = $default_header;
1446                          $data['url']           = $url;
1447                          $data['thumbnail_url'] = vsprintf( $data['thumbnail_url'], $directory_args );
1448                          break;
1449                      }
1450                  }
1451              }
1452          }
1453      }
1454  
1455      $default = array(
1456          'url'           => '',
1457          'thumbnail_url' => '',
1458          'width'         => get_theme_support( 'custom-header', 'width' ),
1459          'height'        => get_theme_support( 'custom-header', 'height' ),
1460          'video'         => get_theme_support( 'custom-header', 'video' ),
1461      );
1462      return (object) wp_parse_args( $data, $default );
1463  }
1464  
1465  /**
1466   * Registers a selection of default headers to be displayed by the custom header admin UI.
1467   *
1468   * @since 3.0.0
1469   *
1470   * @global array $_wp_default_headers
1471   *
1472   * @param array $headers Array of headers keyed by a string ID. The IDs point to arrays
1473   *                       containing 'url', 'thumbnail_url', and 'description' keys.
1474   */
1475  function register_default_headers( $headers ) {
1476      global $_wp_default_headers;
1477  
1478      $_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
1479  }
1480  
1481  /**
1482   * Unregisters default headers.
1483   *
1484   * This function must be called after register_default_headers() has already added the
1485   * header you want to remove.
1486   *
1487   * @see register_default_headers()
1488   * @since 3.0.0
1489   *
1490   * @global array $_wp_default_headers
1491   *
1492   * @param string|array $header The header string id (key of array) to remove, or an array thereof.
1493   * @return bool|void A single header returns true on success, false on failure.
1494   *                   There is currently no return value for multiple headers.
1495   */
1496  function unregister_default_headers( $header ) {
1497      global $_wp_default_headers;
1498      if ( is_array( $header ) ) {
1499          array_map( 'unregister_default_headers', $header );
1500      } elseif ( isset( $_wp_default_headers[ $header ] ) ) {
1501          unset( $_wp_default_headers[ $header ] );
1502          return true;
1503      } else {
1504          return false;
1505      }
1506  }
1507  
1508  /**
1509   * Checks whether a header video is set or not.
1510   *
1511   * @since 4.7.0
1512   *
1513   * @see get_header_video_url()
1514   *
1515   * @return bool Whether a header video is set or not.
1516   */
1517  function has_header_video() {
1518      return (bool) get_header_video_url();
1519  }
1520  
1521  /**
1522   * Retrieves header video URL for custom header.
1523   *
1524   * Uses a local video if present, or falls back to an external video.
1525   *
1526   * @since 4.7.0
1527   *
1528   * @return string|false Header video URL or false if there is no video.
1529   */
1530  function get_header_video_url() {
1531      $id = absint( get_theme_mod( 'header_video' ) );
1532  
1533      if ( $id ) {
1534          // Get the file URL from the attachment ID.
1535          $url = wp_get_attachment_url( $id );
1536      } else {
1537          $url = get_theme_mod( 'external_header_video' );
1538      }
1539  
1540      /**
1541       * Filters the header video URL.
1542       *
1543       * @since 4.7.3
1544       *
1545       * @param string $url Header video URL, if available.
1546       */
1547      $url = apply_filters( 'get_header_video_url', $url );
1548  
1549      if ( ! $id && ! $url ) {
1550          return false;
1551      }
1552  
1553      return esc_url_raw( set_url_scheme( $url ) );
1554  }
1555  
1556  /**
1557   * Displays header video URL.
1558   *
1559   * @since 4.7.0
1560   */
1561  function the_header_video_url() {
1562      $video = get_header_video_url();
1563  
1564      if ( $video ) {
1565          echo esc_url( $video );
1566      }
1567  }
1568  
1569  /**
1570   * Retrieves header video settings.
1571   *
1572   * @since 4.7.0
1573   *
1574   * @return array
1575   */
1576  function get_header_video_settings() {
1577      $header     = get_custom_header();
1578      $video_url  = get_header_video_url();
1579      $video_type = wp_check_filetype( $video_url, wp_get_mime_types() );
1580  
1581      $settings = array(
1582          'mimeType'  => '',
1583          'posterUrl' => get_header_image(),
1584          'videoUrl'  => $video_url,
1585          'width'     => absint( $header->width ),
1586          'height'    => absint( $header->height ),
1587          'minWidth'  => 900,
1588          'minHeight' => 500,
1589          'l10n'      => array(
1590              'pause'      => __( 'Pause' ),
1591              'play'       => __( 'Play' ),
1592              'pauseSpeak' => __( 'Video is paused.' ),
1593              'playSpeak'  => __( 'Video is playing.' ),
1594          ),
1595      );
1596  
1597      if ( preg_match( '#^https?://(?:www\.)?(?:youtube\.com/watch|youtu\.be/)#', $video_url ) ) {
1598          $settings['mimeType'] = 'video/x-youtube';
1599      } elseif ( ! empty( $video_type['type'] ) ) {
1600          $settings['mimeType'] = $video_type['type'];
1601      }
1602  
1603      /**
1604       * Filters header video settings.
1605       *
1606       * @since 4.7.0
1607       *
1608       * @param array $settings An array of header video settings.
1609       */
1610      return apply_filters( 'header_video_settings', $settings );
1611  }
1612  
1613  /**
1614   * Checks whether a custom header is set or not.
1615   *
1616   * @since 4.7.0
1617   *
1618   * @return bool True if a custom header is set. False if not.
1619   */
1620  function has_custom_header() {
1621      if ( has_header_image() || ( has_header_video() && is_header_video_active() ) ) {
1622          return true;
1623      }
1624  
1625      return false;
1626  }
1627  
1628  /**
1629   * Checks whether the custom header video is eligible to show on the current page.
1630   *
1631   * @since 4.7.0
1632   *
1633   * @return bool True if the custom header video should be shown. False if not.
1634   */
1635  function is_header_video_active() {
1636      if ( ! get_theme_support( 'custom-header', 'video' ) ) {
1637          return false;
1638      }
1639  
1640      $video_active_cb = get_theme_support( 'custom-header', 'video-active-callback' );
1641  
1642      if ( empty( $video_active_cb ) || ! is_callable( $video_active_cb ) ) {
1643          $show_video = true;
1644      } else {
1645          $show_video = call_user_func( $video_active_cb );
1646      }
1647  
1648      /**
1649       * Filters whether the custom header video is eligible to show on the current page.
1650       *
1651       * @since 4.7.0
1652       *
1653       * @param bool $show_video Whether the custom header video should be shown. Returns the value
1654       *                         of the theme setting for the `custom-header`'s `video-active-callback`.
1655       *                         If no callback is set, the default value is that of `is_front_page()`.
1656       */
1657      return apply_filters( 'is_header_video_active', $show_video );
1658  }
1659  
1660  /**
1661   * Retrieves the markup for a custom header.
1662   *
1663   * The container div will always be returned in the Customizer preview.
1664   *
1665   * @since 4.7.0
1666   *
1667   * @return string The markup for a custom header on success.
1668   */
1669  function get_custom_header_markup() {
1670      if ( ! has_custom_header() && ! is_customize_preview() ) {
1671          return '';
1672      }
1673  
1674      return sprintf(
1675          '<div id="wp-custom-header" class="wp-custom-header">%s</div>',
1676          get_header_image_tag()
1677      );
1678  }
1679  
1680  /**
1681   * Prints the markup for a custom header.
1682   *
1683   * A container div will always be printed in the Customizer preview.
1684   *
1685   * @since 4.7.0
1686   */
1687  function the_custom_header_markup() {
1688      $custom_header = get_custom_header_markup();
1689      if ( empty( $custom_header ) ) {
1690          return;
1691      }
1692  
1693      echo $custom_header;
1694  
1695      if ( is_header_video_active() && ( has_header_video() || is_customize_preview() ) ) {
1696          wp_enqueue_script( 'wp-custom-header' );
1697          wp_localize_script( 'wp-custom-header', '_wpCustomHeaderSettings', get_header_video_settings() );
1698      }
1699  }
1700  
1701  /**
1702   * Retrieves background image for custom background.
1703   *
1704   * @since 3.0.0
1705   *
1706   * @return string
1707   */
1708  function get_background_image() {
1709      return get_theme_mod( 'background_image', get_theme_support( 'custom-background', 'default-image' ) );
1710  }
1711  
1712  /**
1713   * Displays background image path.
1714   *
1715   * @since 3.0.0
1716   */
1717  function background_image() {
1718      echo get_background_image();
1719  }
1720  
1721  /**
1722   * Retrieves value for custom background color.
1723   *
1724   * @since 3.0.0
1725   *
1726   * @return string
1727   */
1728  function get_background_color() {
1729      return get_theme_mod( 'background_color', get_theme_support( 'custom-background', 'default-color' ) );
1730  }
1731  
1732  /**
1733   * Displays background color value.
1734   *
1735   * @since 3.0.0
1736   */
1737  function background_color() {
1738      echo get_background_color();
1739  }
1740  
1741  /**
1742   * Default custom background callback.
1743   *
1744   * @since 3.0.0
1745   */
1746  function _custom_background_cb() {
1747      // $background is the saved custom image, or the default image.
1748      $background = set_url_scheme( get_background_image() );
1749  
1750      // $color is the saved custom color.
1751      // A default has to be specified in style.css. It will not be printed here.
1752      $color = get_background_color();
1753  
1754      if ( get_theme_support( 'custom-background', 'default-color' ) === $color ) {
1755          $color = false;
1756      }
1757  
1758      $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
1759  
1760      if ( ! $background && ! $color ) {
1761          if ( is_customize_preview() ) {
1762              printf( '<style%s id="custom-background-css"></style>', $type_attr );
1763          }
1764          return;
1765      }
1766  
1767      $style = $color ? "background-color: #$color;" : '';
1768  
1769      if ( $background ) {
1770          $image = ' background-image: url("' . esc_url_raw( $background ) . '");';
1771  
1772          // Background Position.
1773          $position_x = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) );
1774          $position_y = get_theme_mod( 'background_position_y', get_theme_support( 'custom-background', 'default-position-y' ) );
1775  
1776          if ( ! in_array( $position_x, array( 'left', 'center', 'right' ), true ) ) {
1777              $position_x = 'left';
1778          }
1779  
1780          if ( ! in_array( $position_y, array( 'top', 'center', 'bottom' ), true ) ) {
1781              $position_y = 'top';
1782          }
1783  
1784          $position = " background-position: $position_x $position_y;";
1785  
1786          // Background Size.
1787          $size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) );
1788  
1789          if ( ! in_array( $size, array( 'auto', 'contain', 'cover' ), true ) ) {
1790              $size = 'auto';
1791          }
1792  
1793          $size = " background-size: $size;";
1794  
1795          // Background Repeat.
1796          $repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) );
1797  
1798          if ( ! in_array( $repeat, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) {
1799              $repeat = 'repeat';
1800          }
1801  
1802          $repeat = " background-repeat: $repeat;";
1803  
1804          // Background Scroll.
1805          $attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) );
1806  
1807          if ( 'fixed' !== $attachment ) {
1808              $attachment = 'scroll';
1809          }
1810  
1811          $attachment = " background-attachment: $attachment;";
1812  
1813          $style .= $image . $position . $size . $repeat . $attachment;
1814      }
1815      ?>
1816  <style<?php echo $type_attr; ?> id="custom-background-css">
1817  body.custom-background { <?php echo trim( $style ); ?> }
1818  </style>
1819      <?php
1820  }
1821  
1822  /**
1823   * Renders the Custom CSS style element.
1824   *
1825   * @since 4.7.0
1826   */
1827  function wp_custom_css_cb() {
1828      $styles = wp_get_custom_css();
1829      if ( $styles || is_customize_preview() ) :
1830          $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
1831          ?>
1832          <style<?php echo $type_attr; ?> id="wp-custom-css">
1833              <?php echo strip_tags( $styles ); // Note that esc_html() cannot be used because `div &gt; span` is not interpreted properly. ?>
1834          </style>
1835          <?php
1836      endif;
1837  }
1838  
1839  /**
1840   * Fetches the `custom_css` post for a given theme.
1841   *
1842   * @since 4.7.0
1843   *
1844   * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
1845   * @return WP_Post|null The custom_css post or null if none exists.
1846   */
1847  function wp_get_custom_css_post( $stylesheet = '' ) {
1848      if ( empty( $stylesheet ) ) {
1849          $stylesheet = get_stylesheet();
1850      }
1851  
1852      $custom_css_query_vars = array(
1853          'post_type'              => 'custom_css',
1854          'post_status'            => get_post_stati(),
1855          'name'                   => sanitize_title( $stylesheet ),
1856          'posts_per_page'         => 1,
1857          'no_found_rows'          => true,
1858          'cache_results'          => true,
1859          'update_post_meta_cache' => false,
1860          'update_post_term_cache' => false,
1861          'lazy_load_term_meta'    => false,
1862      );
1863  
1864      $post = null;
1865      if ( get_stylesheet() === $stylesheet ) {
1866          $post_id = get_theme_mod( 'custom_css_post_id' );
1867  
1868          if ( $post_id > 0 && get_post( $post_id ) ) {
1869              $post = get_post( $post_id );
1870          }
1871  
1872          // `-1` indicates no post exists; no query necessary.
1873          if ( ! $post && -1 !== $post_id ) {
1874              $query = new WP_Query( $custom_css_query_vars );
1875              $post  = $query->post;
1876              /*
1877               * Cache the lookup. See wp_update_custom_css_post().
1878               * @todo This should get cleared if a custom_css post is added/removed.
1879               */
1880              set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
1881          }
1882      } else {
1883          $query = new WP_Query( $custom_css_query_vars );
1884          $post  = $query->post;
1885      }
1886  
1887      return $post;
1888  }
1889  
1890  /**
1891   * Fetches the saved Custom CSS content for rendering.
1892   *
1893   * @since 4.7.0
1894   *
1895   * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
1896   * @return string The Custom CSS Post content.
1897   */
1898  function wp_get_custom_css( $stylesheet = '' ) {
1899      $css = '';
1900  
1901      if ( empty( $stylesheet ) ) {
1902          $stylesheet = get_stylesheet();
1903      }
1904  
1905      $post = wp_get_custom_css_post( $stylesheet );
1906      if ( $post ) {
1907          $css = $post->post_content;
1908      }
1909  
1910      /**
1911       * Filters the Custom CSS Output into the <head>.
1912       *
1913       * @since 4.7.0
1914       *
1915       * @param string $css        CSS pulled in from the Custom CSS CPT.
1916       * @param string $stylesheet The theme stylesheet name.
1917       */
1918      $css = apply_filters( 'wp_get_custom_css', $css, $stylesheet );
1919  
1920      return $css;
1921  }
1922  
1923  /**
1924   * Updates the `custom_css` post for a given theme.
1925   *
1926   * Inserts a `custom_css` post when one doesn't yet exist.
1927   *
1928   * @since 4.7.0
1929   *
1930   * @param string $css CSS, stored in `post_content`.
1931   * @param array  $args {
1932   *     Args.
1933   *
1934   *     @type string $preprocessed Pre-processed CSS, stored in `post_content_filtered`. Normally empty string. Optional.
1935   *     @type string $stylesheet   Stylesheet (child theme) to update. Optional, defaults to current theme/stylesheet.
1936   * }
1937   * @return WP_Post|WP_Error Post on success, error on failure.
1938   */
1939  function wp_update_custom_css_post( $css, $args = array() ) {
1940      $args = wp_parse_args(
1941          $args,
1942          array(
1943              'preprocessed' => '',
1944              'stylesheet'   => get_stylesheet(),
1945          )
1946      );
1947  
1948      $data = array(
1949          'css'          => $css,
1950          'preprocessed' => $args['preprocessed'],
1951      );
1952  
1953      /**
1954       * Filters the `css` (`post_content`) and `preprocessed` (`post_content_filtered`) args for a `custom_css` post being updated.
1955       *
1956       * This filter can be used by plugin that offer CSS pre-processors, to store the original
1957       * pre-processed CSS in `post_content_filtered` and then store processed CSS in `post_content`.
1958       * When used in this way, the `post_content_filtered` should be supplied as the setting value
1959       * instead of `post_content` via a the `customize_value_custom_css` filter, for example:
1960       *
1961       * <code>
1962       * add_filter( 'customize_value_custom_css', function( $value, $setting ) {
1963       *     $post = wp_get_custom_css_post( $setting->stylesheet );
1964       *     if ( $post && ! empty( $post->post_content_filtered ) ) {
1965       *         $css = $post->post_content_filtered;
1966       *     }
1967       *     return $css;
1968       * }, 10, 2 );
1969       * </code>
1970       *
1971       * @since 4.7.0
1972       * @param array $data {
1973       *     Custom CSS data.
1974       *
1975       *     @type string $css          CSS stored in `post_content`.
1976       *     @type string $preprocessed Pre-processed CSS stored in `post_content_filtered`. Normally empty string.
1977       * }
1978       * @param array $args {
1979       *     The args passed into `wp_update_custom_css_post()` merged with defaults.
1980       *
1981       *     @type string $css          The original CSS passed in to be updated.
1982       *     @type string $preprocessed The original preprocessed CSS passed in to be updated.
1983       *     @type string $stylesheet   The stylesheet (theme) being updated.
1984       * }
1985       */
1986      $data = apply_filters( 'update_custom_css_data', $data, array_merge( $args, compact( 'css' ) ) );
1987  
1988      $post_data = array(
1989          'post_title'            => $args['stylesheet'],
1990          'post_name'             => sanitize_title( $args['stylesheet'] ),
1991          'post_type'             => 'custom_css',
1992          'post_status'           => 'publish',
1993          'post_content'          => $data['css'],
1994          'post_content_filtered' => $data['preprocessed'],
1995      );
1996  
1997      // Update post if it already exists, otherwise create a new one.
1998      $post = wp_get_custom_css_post( $args['stylesheet'] );
1999      if ( $post ) {
2000          $post_data['ID'] = $post->ID;
2001          $r               = wp_update_post( wp_slash( $post_data ), true );
2002      } else {
2003          $r = wp_insert_post( wp_slash( $post_data ), true );
2004  
2005          if ( ! is_wp_error( $r ) ) {
2006              if ( get_stylesheet() === $args['stylesheet'] ) {
2007                  set_theme_mod( 'custom_css_post_id', $r );
2008              }
2009  
2010              // Trigger creation of a revision. This should be removed once #30854 is resolved.
2011              if ( 0 === count( wp_get_post_revisions( $r ) ) ) {
2012                  wp_save_post_revision( $r );
2013              }
2014          }
2015      }
2016  
2017      if ( is_wp_error( $r ) ) {
2018          return $r;
2019      }
2020      return get_post( $r );
2021  }
2022  
2023  /**
2024   * Adds callback for custom TinyMCE editor stylesheets.
2025   *
2026   * The parameter $stylesheet is the name of the stylesheet, relative to
2027   * the theme root. It also accepts an array of stylesheets.
2028   * It is optional and defaults to 'editor-style.css'.
2029   *
2030   * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
2031   * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
2032   * If an array of stylesheets is passed to add_editor_style(),
2033   * RTL is only added for the first stylesheet.
2034   *
2035   * Since version 3.4 the TinyMCE body has .rtl CSS class.
2036   * It is a better option to use that class and add any RTL styles to the main stylesheet.
2037   *
2038   * @since 3.0.0
2039   *
2040   * @global array $editor_styles
2041   *
2042   * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
2043   *                                 Defaults to 'editor-style.css'
2044   */
2045  function add_editor_style( $stylesheet = 'editor-style.css' ) {
2046      global $editor_styles;
2047  
2048      add_theme_support( 'editor-style' );
2049  
2050      $editor_styles = (array) $editor_styles;
2051      $stylesheet    = (array) $stylesheet;
2052  
2053      if ( is_rtl() ) {
2054          $rtl_stylesheet = str_replace( '.css', '-rtl.css', $stylesheet[0] );
2055          $stylesheet[]   = $rtl_stylesheet;
2056      }
2057  
2058      $editor_styles = array_merge( $editor_styles, $stylesheet );
2059  }
2060  
2061  /**
2062   * Removes all visual editor stylesheets.
2063   *
2064   * @since 3.1.0
2065   *
2066   * @global array $editor_styles
2067   *
2068   * @return bool True on success, false if there were no stylesheets to remove.
2069   */
2070  function remove_editor_styles() {
2071      if ( ! current_theme_supports( 'editor-style' ) ) {
2072          return false;
2073      }
2074      _remove_theme_support( 'editor-style' );
2075      if ( is_admin() ) {
2076          $GLOBALS['editor_styles'] = array();
2077      }
2078      return true;
2079  }
2080  
2081  /**
2082   * Retrieves any registered editor stylesheet URLs.
2083   *
2084   * @since 4.0.0
2085   *
2086   * @global array $editor_styles Registered editor stylesheets
2087   *
2088   * @return string[] If registered, a list of editor stylesheet URLs.
2089   */
2090  function get_editor_stylesheets() {
2091      $stylesheets = array();
2092      // Load editor_style.css if the current theme supports it.
2093      if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
2094          $editor_styles = $GLOBALS['editor_styles'];
2095  
2096          $editor_styles = array_unique( array_filter( $editor_styles ) );
2097          $style_uri     = get_stylesheet_directory_uri();
2098          $style_dir     = get_stylesheet_directory();
2099  
2100          // Support externally referenced styles (like, say, fonts).
2101          foreach ( $editor_styles as $key => $file ) {
2102              if ( preg_match( '~^(https?:)?//~', $file ) ) {
2103                  $stylesheets[] = esc_url_raw( $file );
2104                  unset( $editor_styles[ $key ] );
2105              }
2106          }
2107  
2108          // Look in a parent theme first, that way child theme CSS overrides.
2109          if ( is_child_theme() ) {
2110              $template_uri = get_template_directory_uri();
2111              $template_dir = get_template_directory();
2112  
2113              foreach ( $editor_styles as $key => $file ) {
2114                  if ( $file && file_exists( "$template_dir/$file" ) ) {
2115                      $stylesheets[] = "$template_uri/$file";
2116                  }
2117              }
2118          }
2119  
2120          foreach ( $editor_styles as $file ) {
2121              if ( $file && file_exists( "$style_dir/$file" ) ) {
2122                  $stylesheets[] = "$style_uri/$file";
2123              }
2124          }
2125      }
2126  
2127      /**
2128       * Filters the array of URLs of stylesheets applied to the editor.
2129       *
2130       * @since 4.3.0
2131       *
2132       * @param string[] $stylesheets Array of URLs of stylesheets to be applied to the editor.
2133       */
2134      return apply_filters( 'editor_stylesheets', $stylesheets );
2135  }
2136  
2137  /**
2138   * Expands a theme's starter content configuration using core-provided data.
2139   *
2140   * @since 4.7.0
2141   *
2142   * @return array Array of starter content.
2143   */
2144  function get_theme_starter_content() {
2145      $theme_support = get_theme_support( 'starter-content' );
2146      if ( is_array( $theme_support ) && ! empty( $theme_support[0] ) && is_array( $theme_support[0] ) ) {
2147          $config = $theme_support[0];
2148      } else {
2149          $config = array();
2150      }
2151  
2152      $core_content = array(
2153          'widgets'   => array(
2154              'text_business_info' => array(
2155                  'text',
2156                  array(
2157                      'title'  => _x( 'Find Us', 'Theme starter content' ),
2158                      'text'   => implode(
2159                          '',
2160                          array(
2161                              '<strong>' . _x( 'Address', 'Theme starter content' ) . "</strong>\n",
2162                              _x( '123 Main Street', 'Theme starter content' ) . "\n" . _x( 'New York, NY 10001', 'Theme starter content' ) . "\n\n",
2163                              '<strong>' . _x( 'Hours', 'Theme starter content' ) . "</strong>\n",
2164                              _x( 'Monday&ndash;Friday: 9:00AM&ndash;5:00PM', 'Theme starter content' ) . "\n" . _x( 'Saturday &amp; Sunday: 11:00AM&ndash;3:00PM', 'Theme starter content' ),
2165                          )
2166                      ),
2167                      'filter' => true,
2168                      'visual' => true,
2169                  ),
2170              ),
2171              'text_about'         => array(
2172                  'text',
2173                  array(
2174                      'title'  => _x( 'About This Site', 'Theme starter content' ),
2175                      'text'   => _x( 'This may be a good place to introduce yourself and your site or include some credits.', 'Theme starter content' ),
2176                      'filter' => true,
2177                      'visual' => true,
2178                  ),
2179              ),
2180              'archives'           => array(
2181                  'archives',
2182                  array(
2183                      'title' => _x( 'Archives', 'Theme starter content' ),
2184                  ),
2185              ),
2186              'calendar'           => array(
2187                  'calendar',
2188                  array(
2189                      'title' => _x( 'Calendar', 'Theme starter content' ),
2190                  ),
2191              ),
2192              'categories'         => array(
2193                  'categories',
2194                  array(
2195                      'title' => _x( 'Categories', 'Theme starter content' ),
2196                  ),
2197              ),
2198              'meta'               => array(
2199                  'meta',
2200                  array(
2201                      'title' => _x( 'Meta', 'Theme starter content' ),
2202                  ),
2203              ),
2204              'recent-comments'    => array(
2205                  'recent-comments',
2206                  array(
2207                      'title' => _x( 'Recent Comments', 'Theme starter content' ),
2208                  ),
2209              ),
2210              'recent-posts'       => array(
2211                  'recent-posts',
2212                  array(
2213                      'title' => _x( 'Recent Posts', 'Theme starter content' ),
2214                  ),
2215              ),
2216              'search'             => array(
2217                  'search',
2218                  array(
2219                      'title' => _x( 'Search', 'Theme starter content' ),
2220                  ),
2221              ),
2222          ),
2223          'nav_menus' => array(
2224              'link_home'       => array(
2225                  'type'  => 'custom',
2226                  'title' => _x( 'Home', 'Theme starter content' ),
2227                  'url'   => home_url( '/' ),
2228              ),
2229              'page_home'       => array( // Deprecated in favor of 'link_home'.
2230                  'type'      => 'post_type',
2231                  'object'    => 'page',
2232                  'object_id' => '{{home}}',
2233              ),
2234              'page_about'      => array(
2235                  'type'      => 'post_type',
2236                  'object'    => 'page',
2237                  'object_id' => '{{about}}',
2238              ),
2239              'page_blog'       => array(
2240                  'type'      => 'post_type',
2241                  'object'    => 'page',
2242                  'object_id' => '{{blog}}',
2243              ),
2244              'page_news'       => array(
2245                  'type'      => 'post_type',
2246                  'object'    => 'page',
2247                  'object_id' => '{{news}}',
2248              ),
2249              'page_contact'    => array(
2250                  'type'      => 'post_type',
2251                  'object'    => 'page',
2252                  'object_id' => '{{contact}}',
2253              ),
2254  
2255              'link_email'      => array(
2256                  'title' => _x( 'Email', 'Theme starter content' ),
2257                  'url'   => 'mailto:wordpress@example.com',
2258              ),
2259              'link_facebook'   => array(
2260                  'title' => _x( 'Facebook', 'Theme starter content' ),
2261                  'url'   => 'https://www.facebook.com/wordpress',
2262              ),
2263              'link_foursquare' => array(
2264                  'title' => _x( 'Foursquare', 'Theme starter content' ),
2265                  'url'   => 'https://foursquare.com/',
2266              ),
2267              'link_github'     => array(
2268                  'title' => _x( 'GitHub', 'Theme starter content' ),
2269                  'url'   => 'https://github.com/wordpress/',
2270              ),
2271              'link_instagram'  => array(
2272                  'title' => _x( 'Instagram', 'Theme starter content' ),
2273                  'url'   => 'https://www.instagram.com/explore/tags/wordcamp/',
2274              ),
2275              'link_linkedin'   => array(
2276                  'title' => _x( 'LinkedIn', 'Theme starter content' ),
2277                  'url'   => 'https://www.linkedin.com/company/1089783',
2278              ),
2279              'link_pinterest'  => array(
2280                  'title' => _x( 'Pinterest', 'Theme starter content' ),
2281                  'url'   => 'https://www.pinterest.com/',
2282              ),
2283              'link_twitter'    => array(
2284                  'title' => _x( 'Twitter', 'Theme starter content' ),
2285                  'url'   => 'https://twitter.com/wordpress',
2286              ),
2287              'link_yelp'       => array(
2288                  'title' => _x( 'Yelp', 'Theme starter content' ),
2289                  'url'   => 'https://www.yelp.com',
2290              ),
2291              'link_youtube'    => array(
2292                  'title' => _x( 'YouTube', 'Theme starter content' ),
2293                  'url'   => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
2294              ),
2295          ),
2296          'posts'     => array(
2297              'home'             => array(
2298                  'post_type'    => 'page',
2299                  'post_title'   => _x( 'Home', 'Theme starter content' ),
2300                  'post_content' => sprintf(
2301                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2302                      _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' )
2303                  ),
2304              ),
2305              'about'            => array(
2306                  'post_type'    => 'page',
2307                  'post_title'   => _x( 'About', 'Theme starter content' ),
2308                  'post_content' => sprintf(
2309                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2310                      _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' )
2311                  ),
2312              ),
2313              'contact'          => array(
2314                  'post_type'    => 'page',
2315                  'post_title'   => _x( 'Contact', 'Theme starter content' ),
2316                  'post_content' => sprintf(
2317                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2318                      _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' )
2319                  ),
2320              ),
2321              'blog'             => array(
2322                  'post_type'  => 'page',
2323                  'post_title' => _x( 'Blog', 'Theme starter content' ),
2324              ),
2325              'news'             => array(
2326                  'post_type'  => 'page',
2327                  'post_title' => _x( 'News', 'Theme starter content' ),
2328              ),
2329  
2330              'homepage-section' => array(
2331                  'post_type'    => 'page',
2332                  'post_title'   => _x( 'A homepage section', 'Theme starter content' ),
2333                  'post_content' => sprintf(
2334                      "<!-- wp:paragraph -->\n<p>%s</p>\n<!-- /wp:paragraph -->",
2335                      _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' )
2336                  ),
2337              ),
2338          ),
2339      );
2340  
2341      $content = array();
2342  
2343      foreach ( $config as $type => $args ) {
2344          switch ( $type ) {
2345              // Use options and theme_mods as-is.
2346              case 'options':
2347              case 'theme_mods':
2348                  $content[ $type ] = $config[ $type ];
2349                  break;
2350  
2351              // Widgets are grouped into sidebars.
2352              case 'widgets':
2353                  foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
2354                      foreach ( $widgets as $id => $widget ) {
2355                          if ( is_array( $widget ) ) {
2356  
2357                              // Item extends core content.
2358                              if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2359                                  $widget = array(
2360                                      $core_content[ $type ][ $id ][0],
2361                                      array_merge( $core_content[ $type ][ $id ][1], $widget ),
2362                                  );
2363                              }
2364  
2365                              $content[ $type ][ $sidebar_id ][] = $widget;
2366                          } elseif ( is_string( $widget ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $widget ] ) ) {
2367                              $content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
2368                          }
2369                      }
2370                  }
2371                  break;
2372  
2373              // And nav menu items are grouped into nav menus.
2374              case 'nav_menus':
2375                  foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
2376  
2377                      // Ensure nav menus get a name.
2378                      if ( empty( $nav_menu['name'] ) ) {
2379                          $nav_menu['name'] = $nav_menu_location;
2380                      }
2381  
2382                      $content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
2383  
2384                      foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
2385                          if ( is_array( $nav_menu_item ) ) {
2386  
2387                              // Item extends core content.
2388                              if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2389                                  $nav_menu_item = array_merge( $core_content[ $type ][ $id ], $nav_menu_item );
2390                              }
2391  
2392                              $content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
2393                          } elseif ( is_string( $nav_menu_item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $nav_menu_item ] ) ) {
2394                              $content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
2395                          }
2396                      }
2397                  }
2398                  break;
2399  
2400              // Attachments are posts but have special treatment.
2401              case 'attachments':
2402                  foreach ( $config[ $type ] as $id => $item ) {
2403                      if ( ! empty( $item['file'] ) ) {
2404                          $content[ $type ][ $id ] = $item;
2405                      }
2406                  }
2407                  break;
2408  
2409              // All that's left now are posts (besides attachments).
2410              // Not a default case for the sake of clarity and future work.
2411              case 'posts':
2412                  foreach ( $config[ $type ] as $id => $item ) {
2413                      if ( is_array( $item ) ) {
2414  
2415                          // Item extends core content.
2416                          if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2417                              $item = array_merge( $core_content[ $type ][ $id ], $item );
2418                          }
2419  
2420                          // Enforce a subset of fields.
2421                          $content[ $type ][ $id ] = wp_array_slice_assoc(
2422                              $item,
2423                              array(
2424                                  'post_type',
2425                                  'post_title',
2426                                  'post_excerpt',
2427                                  'post_name',
2428                                  'post_content',
2429                                  'menu_order',
2430                                  'comment_status',
2431                                  'thumbnail',
2432                                  'template',
2433                              )
2434                          );
2435                      } elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
2436                          $content[ $type ][ $item ] = $core_content[ $type ][ $item ];
2437                      }
2438                  }
2439                  break;
2440          }
2441      }
2442  
2443      /**
2444       * Filters the expanded array of starter content.
2445       *
2446       * @since 4.7.0
2447       *
2448       * @param array $content Array of starter content.
2449       * @param array $config  Array of theme-specific starter content configuration.
2450       */
2451      return apply_filters( 'get_theme_starter_content', $content, $config );
2452  }
2453  
2454  /**
2455   * Registers theme support for a given feature.
2456   *
2457   * Must be called in the theme's functions.php file to work.
2458   * If attached to a hook, it must be {@see 'after_setup_theme'}.
2459   * The {@see 'init'} hook may be too late for some features.
2460   *
2461   * Example usage:
2462   *
2463   *     add_theme_support( 'title-tag' );
2464   *     add_theme_support( 'custom-logo', array(
2465   *         'height' => 480,
2466   *         'width'  => 720,
2467   *     ) );
2468   *
2469   * @since 2.9.0
2470   * @since 3.4.0 The `custom-header-uploads` feature was deprecated.
2471   * @since 3.6.0 The `html5` feature was added.
2472   * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'.
2473   * @since 4.1.0 The `title-tag` feature was added.
2474   * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added.
2475   * @since 4.7.0 The `starter-content` feature was added.
2476   * @since 5.0.0 The `responsive-embeds`, `align-wide`, `dark-editor-style`, `disable-custom-colors`,
2477   *              `disable-custom-font-sizes`, `editor-color-palette`, `editor-font-sizes`,
2478   *              `editor-styles`, and `wp-block-styles` features were added.
2479   * @since 5.3.0 The `html5` feature now also accepts 'script' and 'style'.
2480   * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
2481   *              by adding it to the function signature.
2482   * @since 5.5.0 The `core-block-patterns` feature was added and is enabled by default.
2483   * @since 5.5.0 The `custom-logo` feature now also accepts 'unlink-homepage-logo'.
2484   * @since 5.6.0 The `post-formats` feature warns if no array is passed.
2485   *
2486   * @global array $_wp_theme_features
2487   *
2488   * @param string $feature The feature being added. Likely core values include 'post-formats', 'post-thumbnails',
2489   *                        'custom-header', 'custom-background', 'custom-logo', 'menus', 'automatic-feed-links',
2490   *                        'html5', 'title-tag', 'customize-selective-refresh-widgets', 'starter-content',
2491   *                        'responsive-embeds', 'align-wide', 'dark-editor-style', 'disable-custom-colors',
2492   *                        'disable-custom-font-sizes', 'editor-color-palette', 'editor-font-sizes',
2493   *                        'editor-styles', 'wp-block-styles', and 'core-block-patterns'.
2494   * @param mixed  ...$args Optional extra arguments to pass along with certain features.
2495   * @return void|bool False on failure, void otherwise.
2496   */
2497  function add_theme_support( $feature, ...$args ) {
2498      global $_wp_theme_features;
2499  
2500      if ( ! $args ) {
2501          $args = true;
2502      }
2503  
2504      switch ( $feature ) {
2505          case 'post-thumbnails':
2506              // All post types are already supported.
2507              if ( true === get_theme_support( 'post-thumbnails' ) ) {
2508                  return;
2509              }
2510  
2511              /*
2512               * Merge post types with any that already declared their support
2513               * for post thumbnails.
2514               */
2515              if ( isset( $args[0] ) && is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
2516                  $args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
2517              }
2518  
2519              break;
2520  
2521          case 'post-formats':
2522              if ( isset( $args[0] ) && is_array( $args[0] ) ) {
2523                  $post_formats = get_post_format_slugs();
2524                  unset( $post_formats['standard'] );
2525  
2526                  $args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
2527              } else {
2528                  _doing_it_wrong( "add_theme_support( 'post-formats' )", __( 'You need to pass an array of post formats.' ), '5.6.0' );
2529                  return false;
2530              }
2531              break;
2532  
2533          case 'html5':
2534              // You can't just pass 'html5', you need to pass an array of types.
2535              if ( empty( $args[0] ) ) {
2536                  // Build an array of types for back-compat.
2537                  $args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
2538              } elseif ( ! isset( $args[0] ) || ! is_array( $args[0] ) ) {
2539                  _doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
2540                  return false;
2541              }
2542  
2543              // Calling 'html5' again merges, rather than overwrites.
2544              if ( isset( $_wp_theme_features['html5'] ) ) {
2545                  $args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
2546              }
2547              break;
2548  
2549          case 'custom-logo':
2550              if ( true === $args ) {
2551                  $args = array( 0 => array() );
2552              }
2553              $defaults = array(
2554                  'width'                => null,
2555                  'height'               => null,
2556                  'flex-width'           => false,
2557                  'flex-height'          => false,
2558                  'header-text'          => '',
2559                  'unlink-homepage-logo' => false,
2560              );
2561              $args[0]  = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
2562  
2563              // Allow full flexibility if no size is specified.
2564              if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
2565                  $args[0]['flex-width']  = true;
2566                  $args[0]['flex-height'] = true;
2567              }
2568              break;
2569  
2570          case 'custom-header-uploads':
2571              return add_theme_support( 'custom-header', array( 'uploads' => true ) );
2572  
2573          case 'custom-header':
2574              if ( true === $args ) {
2575                  $args = array( 0 => array() );
2576              }
2577  
2578              $defaults = array(
2579                  'default-image'          => '',
2580                  'random-default'         => false,
2581                  'width'                  => 0,
2582                  'height'                 => 0,
2583                  'flex-height'            => false,
2584                  'flex-width'             => false,
2585                  'default-text-color'     => '',
2586                  'header-text'            => true,
2587                  'uploads'                => true,
2588                  'wp-head-callback'       => '',
2589                  'admin-head-callback'    => '',
2590                  'admin-preview-callback' => '',
2591                  'video'                  => false,
2592                  'video-active-callback'  => 'is_front_page',
2593              );
2594  
2595              $jit = isset( $args[0]['__jit'] );
2596              unset( $args[0]['__jit'] );
2597  
2598              // Merge in data from previous add_theme_support() calls.
2599              // The first value registered wins. (A child theme is set up first.)
2600              if ( isset( $_wp_theme_features['custom-header'] ) ) {
2601                  $args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
2602              }
2603  
2604              // Load in the defaults at the end, as we need to insure first one wins.
2605              // This will cause all constants to be defined, as each arg will then be set to the default.
2606              if ( $jit ) {
2607                  $args[0] = wp_parse_args( $args[0], $defaults );
2608              }
2609  
2610              /*
2611               * If a constant was defined, use that value. Otherwise, define the constant to ensure
2612               * the constant is always accurate (and is not defined later,  overriding our value).
2613               * As stated above, the first value wins.
2614               * Once we get to wp_loaded (just-in-time), define any constants we haven't already.
2615               * Constants are lame. Don't reference them. This is just for backward compatibility.
2616               */
2617  
2618              if ( defined( 'NO_HEADER_TEXT' ) ) {
2619                  $args[0]['header-text'] = ! NO_HEADER_TEXT;
2620              } elseif ( isset( $args[0]['header-text'] ) ) {
2621                  define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
2622              }
2623  
2624              if ( defined( 'HEADER_IMAGE_WIDTH' ) ) {
2625                  $args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
2626              } elseif ( isset( $args[0]['width'] ) ) {
2627                  define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
2628              }
2629  
2630              if ( defined( 'HEADER_IMAGE_HEIGHT' ) ) {
2631                  $args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
2632              } elseif ( isset( $args[0]['height'] ) ) {
2633                  define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
2634              }
2635  
2636              if ( defined( 'HEADER_TEXTCOLOR' ) ) {
2637                  $args[0]['default-text-color'] = HEADER_TEXTCOLOR;
2638              } elseif ( isset( $args[0]['default-text-color'] ) ) {
2639                  define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
2640              }
2641  
2642              if ( defined( 'HEADER_IMAGE' ) ) {
2643                  $args[0]['default-image'] = HEADER_IMAGE;
2644              } elseif ( isset( $args[0]['default-image'] ) ) {
2645                  define( 'HEADER_IMAGE', $args[0]['default-image'] );
2646              }
2647  
2648              if ( $jit && ! empty( $args[0]['default-image'] ) ) {
2649                  $args[0]['random-default'] = false;
2650              }
2651  
2652              // If headers are supported, and we still don't have a defined width or height,
2653              // we have implicit flex sizes.
2654              if ( $jit ) {
2655                  if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) ) {
2656                      $args[0]['flex-width'] = true;
2657                  }
2658                  if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) ) {
2659                      $args[0]['flex-height'] = true;
2660                  }
2661              }
2662  
2663              break;
2664  
2665          case 'custom-background':
2666              if ( true === $args ) {
2667                  $args = array( 0 => array() );
2668              }
2669  
2670              $defaults = array(
2671                  'default-image'          => '',
2672                  'default-preset'         => 'default',
2673                  'default-position-x'     => 'left',
2674                  'default-position-y'     => 'top',
2675                  'default-size'           => 'auto',
2676                  'default-repeat'         => 'repeat',
2677                  'default-attachment'     => 'scroll',
2678                  'default-color'          => '',
2679                  'wp-head-callback'       => '_custom_background_cb',
2680                  'admin-head-callback'    => '',
2681                  'admin-preview-callback' => '',
2682              );
2683  
2684              $jit = isset( $args[0]['__jit'] );
2685              unset( $args[0]['__jit'] );
2686  
2687              // Merge in data from previous add_theme_support() calls. The first value registered wins.
2688              if ( isset( $_wp_theme_features['custom-background'] ) ) {
2689                  $args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
2690              }
2691  
2692              if ( $jit ) {
2693                  $args[0] = wp_parse_args( $args[0], $defaults );
2694              }
2695  
2696              if ( defined( 'BACKGROUND_COLOR' ) ) {
2697                  $args[0]['default-color'] = BACKGROUND_COLOR;
2698              } elseif ( isset( $args[0]['default-color'] ) || $jit ) {
2699                  define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
2700              }
2701  
2702              if ( defined( 'BACKGROUND_IMAGE' ) ) {
2703                  $args[0]['default-image'] = BACKGROUND_IMAGE;
2704              } elseif ( isset( $args[0]['default-image'] ) || $jit ) {
2705                  define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
2706              }
2707  
2708              break;
2709  
2710          // Ensure that 'title-tag' is accessible in the admin.
2711          case 'title-tag':
2712              // Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
2713              if ( did_action( 'wp_loaded' ) ) {
2714                  _doing_it_wrong(
2715                      "add_theme_support( 'title-tag' )",
2716                      sprintf(
2717                          /* translators: 1: title-tag, 2: wp_loaded */
2718                          __( 'Theme support for %1$s should be registered before the %2$s hook.' ),
2719                          '<code>title-tag</code>',
2720                          '<code>wp_loaded</code>'
2721                      ),
2722                      '4.1.0'
2723                  );
2724  
2725                  return false;
2726              }
2727      }
2728  
2729      $_wp_theme_features[ $feature ] = $args;
2730  }
2731  
2732  /**
2733   * Registers the internal custom header and background routines.
2734   *
2735   * @since 3.4.0
2736   * @access private
2737   *
2738   * @global Custom_Image_Header $custom_image_header
2739   * @global Custom_Background   $custom_background
2740   */
2741  function _custom_header_background_just_in_time() {
2742      global $custom_image_header, $custom_background;
2743  
2744      if ( current_theme_supports( 'custom-header' ) ) {
2745          // In case any constants were defined after an add_custom_image_header() call, re-run.
2746          add_theme_support( 'custom-header', array( '__jit' => true ) );
2747  
2748          $args = get_theme_support( 'custom-header' );
2749          if ( $args[0]['wp-head-callback'] ) {
2750              add_action( 'wp_head', $args[0]['wp-head-callback'] );
2751          }
2752  
2753          if ( is_admin() ) {
2754              require_once ABSPATH . 'wp-admin/includes/class-custom-image-header.php';
2755              $custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2756          }
2757      }
2758  
2759      if ( current_theme_supports( 'custom-background' ) ) {
2760          // In case any constants were defined after an add_custom_background() call, re-run.
2761          add_theme_support( 'custom-background', array( '__jit' => true ) );
2762  
2763          $args = get_theme_support( 'custom-background' );
2764          add_action( 'wp_head', $args[0]['wp-head-callback'] );
2765  
2766          if ( is_admin() ) {
2767              require_once ABSPATH . 'wp-admin/includes/class-custom-background.php';
2768              $custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2769          }
2770      }
2771  }
2772  
2773  /**
2774   * Adds CSS to hide header text for custom logo, based on Customizer setting.
2775   *
2776   * @since 4.5.0
2777   * @access private
2778   */
2779  function _custom_logo_header_styles() {
2780      if ( ! current_theme_supports( 'custom-header', 'header-text' ) && get_theme_support( 'custom-logo', 'header-text' ) && ! get_theme_mod( 'header_text', true ) ) {
2781          $classes = (array) get_theme_support( 'custom-logo', 'header-text' );
2782          $classes = array_map( 'sanitize_html_class', $classes );
2783          $classes = '.' . implode( ', .', $classes );
2784  
2785          $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"';
2786          ?>
2787          <!-- Custom Logo: hide header text -->
2788          <style id="custom-logo-css"<?php echo $type_attr; ?>>
2789              <?php echo $classes; ?> {
2790                  position: absolute;
2791                  clip: rect(1px, 1px, 1px, 1px);
2792              }
2793          </style>
2794          <?php
2795      }
2796  }
2797  
2798  /**
2799   * Gets the theme support arguments passed when registering that support.
2800   *
2801   * Example usage:
2802   *
2803   *     get_theme_support( 'custom-logo' );
2804   *     get_theme_support( 'custom-header', 'width' );
2805   *
2806   * @since 3.1.0
2807   * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
2808   *              by adding it to the function signature.
2809   *
2810   * @global array $_wp_theme_features
2811   *
2812   * @param string $feature The feature to check. See add_theme_support() for the list
2813   *                        of possible values.
2814   * @param mixed  ...$args Optional extra arguments to be checked against certain features.
2815   * @return mixed The array of extra arguments or the value for the registered feature.
2816   */
2817  function get_theme_support( $feature, ...$args ) {
2818      global $_wp_theme_features;
2819      if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
2820          return false;
2821      }
2822  
2823      if ( ! $args ) {
2824          return $_wp_theme_features[ $feature ];
2825      }
2826  
2827      switch ( $feature ) {
2828          case 'custom-logo':
2829          case 'custom-header':
2830          case 'custom-background':
2831              if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) ) {
2832                  return $_wp_theme_features[ $feature ][0][ $args[0] ];
2833              }
2834              return false;
2835  
2836          default:
2837              return $_wp_theme_features[ $feature ];
2838      }
2839  }
2840  
2841  /**
2842   * Allows a theme to de-register its support of a certain feature
2843   *
2844   * Should be called in the theme's functions.php file. Generally would
2845   * be used for child themes to override support from the parent theme.
2846   *
2847   * @since 3.0.0
2848   *
2849   * @see add_theme_support()
2850   *
2851   * @param string $feature The feature being removed. See add_theme_support() for the list
2852   *                        of possible values.
2853   * @return bool|void Whether feature was removed.
2854   */
2855  function remove_theme_support( $feature ) {
2856      // Do not remove internal registrations that are not used directly by themes.
2857      if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ), true ) ) {
2858          return false;
2859      }
2860  
2861      return _remove_theme_support( $feature );
2862  }
2863  
2864  /**
2865   * Do not use. Removes theme support internally without knowledge of those not used
2866   * by themes directly.
2867   *
2868   * @access private
2869   * @since 3.1.0
2870   * @global array               $_wp_theme_features
2871   * @global Custom_Image_Header $custom_image_header
2872   * @global Custom_Background   $custom_background
2873   *
2874   * @param string $feature The feature being removed. See add_theme_support() for the list
2875   *                        of possible values.
2876   * @return bool True if support was removed, false if the feature was not registered.
2877   */
2878  function _remove_theme_support( $feature ) {
2879      global $_wp_theme_features;
2880  
2881      switch ( $feature ) {
2882          case 'custom-header-uploads':
2883              if ( ! isset( $_wp_theme_features['custom-header'] ) ) {
2884                  return false;
2885              }
2886              add_theme_support( 'custom-header', array( 'uploads' => false ) );
2887              return; // Do not continue - custom-header-uploads no longer exists.
2888      }
2889  
2890      if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
2891          return false;
2892      }
2893  
2894      switch ( $feature ) {
2895          case 'custom-header':
2896              if ( ! did_action( 'wp_loaded' ) ) {
2897                  break;
2898              }
2899              $support = get_theme_support( 'custom-header' );
2900              if ( isset( $support[0]['wp-head-callback'] ) ) {
2901                  remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2902              }
2903              if ( isset( $GLOBALS['custom_image_header'] ) ) {
2904                  remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
2905                  unset( $GLOBALS['custom_image_header'] );
2906              }
2907              break;
2908  
2909          case 'custom-background':
2910              if ( ! did_action( 'wp_loaded' ) ) {
2911                  break;
2912              }
2913              $support = get_theme_support( 'custom-background' );
2914              if ( isset( $support[0]['wp-head-callback'] ) ) {
2915                  remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2916              }
2917              remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
2918              unset( $GLOBALS['custom_background'] );
2919              break;
2920      }
2921  
2922      unset( $_wp_theme_features[ $feature ] );
2923  
2924      return true;
2925  }
2926  
2927  /**
2928   * Checks a theme's support for a given feature.
2929   *
2930   * Example usage:
2931   *
2932   *     current_theme_supports( 'custom-logo' );
2933   *     current_theme_supports( 'html5', 'comment-form' );
2934   *
2935   * @since 2.9.0
2936   * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
2937   *              by adding it to the function signature.
2938   *
2939   * @global array $_wp_theme_features
2940   *
2941   * @param string $feature The feature being checked. See add_theme_support() for the list
2942   *                        of possible values.
2943   * @param mixed  ...$args Optional extra arguments to be checked against certain features.
2944   * @return bool True if the current theme supports the feature, false otherwise.
2945   */
2946  function current_theme_supports( $feature, ...$args ) {
2947      global $_wp_theme_features;
2948  
2949      if ( 'custom-header-uploads' === $feature ) {
2950          return current_theme_supports( 'custom-header', 'uploads' );
2951      }
2952  
2953      if ( ! isset( $_wp_theme_features[ $feature ] ) ) {
2954          return false;
2955      }
2956  
2957      // If no args passed then no extra checks need be performed.
2958      if ( ! $args ) {
2959          return true;
2960      }
2961  
2962      switch ( $feature ) {
2963          case 'post-thumbnails':
2964              /*
2965               * post-thumbnails can be registered for only certain content/post types
2966               * by passing an array of types to add_theme_support().
2967               * If no array was passed, then any type is accepted.
2968               */
2969              if ( true === $_wp_theme_features[ $feature ] ) {  // Registered for all types.
2970                  return true;
2971              }
2972              $content_type = $args[0];
2973              return in_array( $content_type, $_wp_theme_features[ $feature ][0], true );
2974  
2975          case 'html5':
2976          case 'post-formats':
2977              /*
2978               * Specific post formats can be registered by passing an array of types
2979               * to add_theme_support().
2980               *
2981               * Specific areas of HTML5 support *must* be passed via an array to add_theme_support().
2982               */
2983              $type = $args[0];
2984              return in_array( $type, $_wp_theme_features[ $feature ][0], true );
2985  
2986          case 'custom-logo':
2987          case 'custom-header':
2988          case 'custom-background':
2989              // Specific capabilities can be registered by passing an array to add_theme_support().
2990              return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
2991      }
2992  
2993      /**
2994       * Filters whether the current theme supports a specific feature.
2995       *
2996       * The dynamic portion of the hook name, `$feature`, refers to the specific
2997       * theme feature. See add_theme_support() for the list of possible values.
2998       *
2999       * @since 3.4.0
3000       *
3001       * @param bool   $supports Whether the current theme supports the given feature. Default true.
3002       * @param array  $args     Array of arguments for the feature.
3003       * @param string $feature  The theme feature.
3004       */
3005      return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[ $feature ] ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
3006  }
3007  
3008  /**
3009   * Checks a theme's support for a given feature before loading the functions which implement it.
3010   *
3011   * @since 2.9.0
3012   *
3013   * @param string $feature The feature being checked. See add_theme_support() for the list
3014   *                        of possible values.
3015   * @param string $include Path to the file.
3016   * @return bool True if the current theme supports the supplied feature, false otherwise.
3017   */
3018  function require_if_theme_supports( $feature, $include ) {
3019      if ( current_theme_supports( $feature ) ) {
3020          require $include;
3021          return true;
3022      }
3023      return false;
3024  }
3025  
3026  /**
3027   * Registers a theme feature for use in add_theme_support().
3028   *
3029   * This does not indicate that the current theme supports the feature, it only describes
3030   * the feature's supported options.
3031   *
3032   * @since 5.5.0
3033   *
3034   * @see add_theme_support()
3035   *
3036   * @global array $_wp_registered_theme_features
3037   *
3038   * @param string $feature The name uniquely identifying the feature. See add_theme_support()
3039   *                        for the list of possible values.
3040   * @param array  $args {
3041   *     Data used to describe the theme.
3042   *
3043   *     @type string     $type         The type of data associated with this feature.
3044   *                                    Valid values are 'string', 'boolean', 'integer',
3045   *                                    'number', 'array', and 'object'. Defaults to 'boolean'.
3046   *     @type boolean    $variadic     Does this feature utilize the variadic support
3047   *                                    of add_theme_support(), or are all arguments specified
3048   *                                    as the second parameter. Must be used with the "array" type.
3049   *     @type string     $description  A short description of the feature. Included in
3050   *                                    the Themes REST API schema. Intended for developers.
3051   *     @type bool|array $show_in_rest {
3052   *         Whether this feature should be included in the Themes REST API endpoint.
3053   *         Defaults to not being included. When registering an 'array' or 'object' type,
3054   *         this argument must be an array with the 'schema' key.
3055   *
3056   *         @type array    $schema           Specifies the JSON Schema definition describing
3057   *                                          the feature. If any objects in the schema do not include
3058   *                                          the 'additionalProperties' keyword, it is set to false.
3059   *         @type string   $name             An alternate name to be used as the property name
3060   *                                          in the REST API.
3061   *         @type callable $prepare_callback A function used to format the theme support in the REST API.
3062   *                                          Receives the raw theme support value.
3063   *      }
3064   * }
3065   * @return true|WP_Error True if the theme feature was successfully registered, a WP_Error object if not.
3066   */
3067  function register_theme_feature( $feature, $args = array() ) {
3068      global $_wp_registered_theme_features;
3069  
3070      if ( ! is_array( $_wp_registered_theme_features ) ) {
3071          $_wp_registered_theme_features = array();
3072      }
3073  
3074      $defaults = array(
3075          'type'         => 'boolean',
3076          'variadic'     => false,
3077          'description'  => '',
3078          'show_in_rest' => false,
3079      );
3080  
3081      $args = wp_parse_args( $args, $defaults );
3082  
3083      if ( true === $args['show_in_rest'] ) {
3084          $args['show_in_rest'] = array();
3085      }
3086  
3087      if ( is_array( $args['show_in_rest'] ) ) {
3088          $args['show_in_rest'] = wp_parse_args(
3089              $args['show_in_rest'],
3090              array(
3091                  'schema'           => array(),
3092                  'name'             => $feature,
3093                  'prepare_callback' => null,
3094              )
3095          );
3096      }
3097  
3098      if ( ! in_array( $args['type'], array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
3099          return new WP_Error(
3100              'invalid_type',
3101              __( 'The feature "type" is not valid JSON Schema type.' )
3102          );
3103      }
3104  
3105      if ( true === $args['variadic'] && 'array' !== $args['type'] ) {
3106          return new WP_Error(
3107              'variadic_must_be_array',
3108              __( 'When registering a "variadic" theme feature, the "type" must be an "array".' )
3109          );
3110      }
3111  
3112      if ( false !== $args['show_in_rest'] && in_array( $args['type'], array( 'array', 'object' ), true ) ) {
3113          if ( ! is_array( $args['show_in_rest'] ) || empty( $args['show_in_rest']['schema'] ) ) {
3114              return new WP_Error(
3115                  'missing_schema',
3116                  __( 'When registering an "array" or "object" feature to show in the REST API, the feature\'s schema must also be defined.' )
3117              );
3118          }
3119  
3120          if ( 'array' === $args['type'] && ! isset( $args['show_in_rest']['schema']['items'] ) ) {
3121              return new WP_Error(
3122                  'missing_schema_items',
3123                  __( 'When registering an "array" feature, the feature\'s schema must include the "items" keyword.' )
3124              );
3125          }
3126  
3127          if ( 'object' === $args['type'] && ! isset( $args['show_in_rest']['schema']['properties'] ) ) {
3128              return new WP_Error(
3129                  'missing_schema_properties',
3130                  __( 'When registering an "object" feature, the feature\'s schema must include the "properties" keyword.' )
3131              );
3132          }
3133      }
3134  
3135      if ( is_array( $args['show_in_rest'] ) ) {
3136          if ( isset( $args['show_in_rest']['prepare_callback'] ) && ! is_callable( $args['show_in_rest']['prepare_callback'] ) ) {
3137              return new WP_Error(
3138                  'invalid_rest_prepare_callback',
3139                  sprintf(
3140                      /* translators: %s: prepare_callback */
3141                      __( 'The "%s" must be a callable function.' ),
3142                      'prepare_callback'
3143                  )
3144              );
3145          }
3146  
3147          $args['show_in_rest']['schema'] = wp_parse_args(
3148              $args['show_in_rest']['schema'],
3149              array(
3150                  'description' => $args['description'],
3151                  'type'        => $args['type'],
3152                  'default'     => false,
3153              )
3154          );
3155  
3156          if ( is_bool( $args['show_in_rest']['schema']['default'] )
3157              && ! in_array( 'boolean', (array) $args['show_in_rest']['schema']['type'], true )
3158          ) {
3159              // Automatically include the "boolean" type when the default value is a boolean.
3160              $args['show_in_rest']['schema']['type'] = (array) $args['show_in_rest']['schema']['type'];
3161              array_unshift( $args['show_in_rest']['schema']['type'], 'boolean' );
3162          }
3163  
3164          $args['show_in_rest']['schema'] = rest_default_additional_properties_to_false( $args['show_in_rest']['schema'] );
3165      }
3166  
3167      $_wp_registered_theme_features[ $feature ] = $args;
3168  
3169      return true;
3170  }
3171  
3172  /**
3173   * Gets the list of registered theme features.
3174   *
3175   * @since 5.5.0
3176   *
3177   * @global array $_wp_registered_theme_features
3178   *
3179   * @return array[] List of theme features, keyed by their name.
3180   */
3181  function get_registered_theme_features() {
3182      global $_wp_registered_theme_features;
3183  
3184      if ( ! is_array( $_wp_registered_theme_features ) ) {
3185          return array();
3186      }
3187  
3188      return $_wp_registered_theme_features;
3189  }
3190  
3191  /**
3192   * Gets the registration config for a theme feature.
3193   *
3194   * @since 5.5.0
3195   *
3196   * @global array $_wp_registered_theme_features
3197   *
3198   * @param string $feature The feature name. See add_theme_support() for the list
3199   *                        of possible values.
3200   * @return array|null The registration args, or null if the feature was not registered.
3201   */
3202  function get_registered_theme_feature( $feature ) {
3203      global $_wp_registered_theme_features;
3204  
3205      if ( ! is_array( $_wp_registered_theme_features ) ) {
3206          return null;
3207      }
3208  
3209      return isset( $_wp_registered_theme_features[ $feature ] ) ? $_wp_registered_theme_features[ $feature ] : null;
3210  }
3211  
3212  /**
3213   * Checks an attachment being deleted to see if it's a header or background image.
3214   *
3215   * If true it removes the theme modification which would be pointing at the deleted
3216   * attachment.
3217   *
3218   * @access private
3219   * @since 3.0.0
3220   * @since 4.3.0 Also removes `header_image_data`.
3221   * @since 4.5.0 Also removes custom logo theme mods.
3222   *
3223   * @param int $id The attachment ID.
3224   */
3225  function _delete_attachment_theme_mod( $id ) {
3226      $attachment_image = wp_get_attachment_url( $id );
3227      $header_image     = get_header_image();
3228      $background_image = get_background_image();
3229      $custom_logo_id   = get_theme_mod( 'custom_logo' );
3230  
3231      if ( $custom_logo_id && $custom_logo_id == $id ) {
3232          remove_theme_mod( 'custom_logo' );
3233          remove_theme_mod( 'header_text' );
3234      }
3235  
3236      if ( $header_image && $header_image == $attachment_image ) {
3237          remove_theme_mod( 'header_image' );
3238          remove_theme_mod( 'header_image_data' );
3239      }
3240  
3241      if ( $background_image && $background_image == $attachment_image ) {
3242          remove_theme_mod( 'background_image' );
3243      }
3244  }
3245  
3246  /**
3247   * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
3248   *
3249   * See {@see 'after_switch_theme'}.
3250   *
3251   * @since 3.3.0
3252   */
3253  function check_theme_switched() {
3254      $stylesheet = get_option( 'theme_switched' );
3255      if ( $stylesheet ) {
3256          $old_theme = wp_get_theme( $stylesheet );
3257  
3258          // Prevent widget & menu mapping from running since Customizer already called it up front.
3259          if ( get_option( 'theme_switched_via_customizer' ) ) {
3260              remove_action( 'after_switch_theme', '_wp_menus_changed' );
3261              remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
3262              update_option( 'theme_switched_via_customizer', false );
3263          }
3264  
3265          if ( $old_theme->exists() ) {
3266              /**
3267               * Fires on the first WP load after a theme switch if the old theme still exists.
3268               *
3269               * This action fires multiple times and the parameters differs
3270               * according to the context, if the old theme exists or not.
3271               * If the old theme is missing, the parameter will be the slug
3272               * of the old theme.
3273               *
3274               * @since 3.3.0
3275               *
3276               * @param string   $old_name  Old theme name.
3277               * @param WP_Theme $old_theme WP_Theme instance of the old theme.
3278               */
3279              do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
3280          } else {
3281              /** This action is documented in wp-includes/theme.php */
3282              do_action( 'after_switch_theme', $stylesheet, $old_theme );
3283          }
3284          flush_rewrite_rules();
3285  
3286          update_option( 'theme_switched', false );
3287      }
3288  }
3289  
3290  /**
3291   * Includes and instantiates the WP_Customize_Manager class.
3292   *
3293   * Loads the Customizer at plugins_loaded when accessing the customize.php admin
3294   * page or when any request includes a wp_customize=on param or a customize_changeset
3295   * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
3296   * WordPress is loading, especially in the Customizer preview
3297   * or when making Customizer Ajax requests for widgets or menus.
3298   *
3299   * @since 3.4.0
3300   *
3301   * @global WP_Customize_Manager $wp_customize
3302   */
3303  function _wp_customize_include() {
3304  
3305      $is_customize_admin_page = ( is_admin() && 'customize.php' === basename( $_SERVER['PHP_SELF'] ) );
3306      $should_include          = (
3307          $is_customize_admin_page
3308          ||
3309          ( isset( $_REQUEST['wp_customize'] ) && 'on' === $_REQUEST['wp_customize'] )
3310          ||
3311          ( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
3312      );
3313  
3314      if ( ! $should_include ) {
3315          return;
3316      }
3317  
3318      /*
3319       * Note that wp_unslash() is not being used on the input vars because it is
3320       * called before wp_magic_quotes() gets called. Besides this fact, none of
3321       * the values should contain any characters needing slashes anyway.
3322       */
3323      $keys       = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel', 'customize_autosaved' );
3324      $input_vars = array_merge(
3325          wp_array_slice_assoc( $_GET, $keys ),
3326          wp_array_slice_assoc( $_POST, $keys )
3327      );
3328  
3329      $theme             = null;
3330      $autosaved         = null;
3331      $messenger_channel = null;
3332  
3333      // Value false indicates UUID should be determined after_setup_theme
3334      // to either re-use existing saved changeset or else generate a new UUID if none exists.
3335      $changeset_uuid = false;
3336  
3337      // Set initially fo false since defaults to true for back-compat;
3338      // can be overridden via the customize_changeset_branching filter.
3339      $branching = false;
3340  
3341      if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
3342          $changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
3343      } elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
3344          $changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
3345      }
3346  
3347      // Note that theme will be sanitized via WP_Theme.
3348      if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
3349          $theme = $input_vars['theme'];
3350      } elseif ( isset( $input_vars['customize_theme'] ) ) {
3351          $theme = $input_vars['customize_theme'];
3352      }
3353  
3354      if ( ! empty( $input_vars['customize_autosaved'] ) ) {
3355          $autosaved = true;
3356      }
3357  
3358      if ( isset( $input_vars['customize_messenger_channel'] ) ) {
3359          $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
3360      }
3361  
3362      /*
3363       * Note that settings must be previewed even outside the customizer preview
3364       * and also in the customizer pane itself. This is to enable loading an existing
3365       * changeset into the customizer. Previewing the settings only has to be prevented
3366       * here in the case of a customize_save action because this will cause WP to think
3367       * there is nothing changed that needs to be saved.
3368       */
3369      $is_customize_save_action = (
3370          wp_doing_ajax()
3371          &&
3372          isset( $_REQUEST['action'] )
3373          &&
3374          'customize_save' === wp_unslash( $_REQUEST['action'] )
3375      );
3376      $settings_previewed       = ! $is_customize_save_action;
3377  
3378      require_once  ABSPATH . WPINC . '/class-wp-customize-manager.php';
3379      $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel', 'settings_previewed', 'autosaved', 'branching' ) );
3380  }
3381  
3382  /**
3383   * Publishes a snapshot's changes.
3384   *
3385   * @since 4.7.0
3386   * @access private
3387   *
3388   * @global wpdb                 $wpdb         WordPress database abstraction object.
3389   * @global WP_Customize_Manager $wp_customize Customizer instance.
3390   *
3391   * @param string  $new_status     New post status.
3392   * @param string  $old_status     Old post status.
3393   * @param WP_Post $changeset_post Changeset post object.
3394   */
3395  function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
3396      global $wp_customize, $wpdb;
3397  
3398      $is_publishing_changeset = (
3399          'customize_changeset' === $changeset_post->post_type
3400          &&
3401          'publish' === $new_status
3402          &&
3403          'publish' !== $old_status
3404      );
3405      if ( ! $is_publishing_changeset ) {
3406          return;
3407      }
3408  
3409      if ( empty( $wp_customize ) ) {
3410          require_once  ABSPATH . WPINC . '/class-wp-customize-manager.php';
3411          $wp_customize = new WP_Customize_Manager(
3412              array(
3413                  'changeset_uuid'     => $changeset_post->post_name,
3414                  'settings_previewed' => false,
3415              )
3416          );
3417      }
3418  
3419      if ( ! did_action( 'customize_register' ) ) {
3420          /*
3421           * When running from CLI or Cron, the customize_register action will need
3422           * to be triggered in order for core, themes, and plugins to register their
3423           * settings. Normally core will add_action( 'customize_register' ) at
3424           * priority 10 to register the core settings, and if any themes/plugins
3425           * also add_action( 'customize_register' ) at the same priority, they
3426           * will have a $wp_customize with those settings registered since they
3427           * call add_action() afterward, normally. However, when manually doing
3428           * the customize_register action after the setup_theme, then the order
3429           * will be reversed for two actions added at priority 10, resulting in
3430           * the core settings no longer being available as expected to themes/plugins.
3431           * So the following manually calls the method that registers the core
3432           * settings up front before doing the action.
3433           */
3434          remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
3435          $wp_customize->register_controls();
3436  
3437          /** This filter is documented in /wp-includes/class-wp-customize-manager.php */
3438          do_action( 'customize_register', $wp_customize );
3439      }
3440      $wp_customize->_publish_changeset_values( $changeset_post->ID );
3441  
3442      /*
3443       * Trash the changeset post if revisions are not enabled. Unpublished
3444       * changesets by default get garbage collected due to the auto-draft status.
3445       * When a changeset post is published, however, it would no longer get cleaned
3446       * out. This is a problem when the changeset posts are never displayed anywhere,
3447       * since they would just be endlessly piling up. So here we use the revisions
3448       * feature to indicate whether or not a published changeset should get trashed
3449       * and thus garbage collected.
3450       */
3451      if ( ! wp_revisions_enabled( $changeset_post ) ) {
3452          $wp_customize->trash_changeset_post( $changeset_post->ID );
3453      }
3454  }
3455  
3456  /**
3457   * Filters changeset post data upon insert to ensure post_name is intact.
3458   *
3459   * This is needed to prevent the post_name from being dropped when the post is
3460   * transitioned into pending status by a contributor.
3461   *
3462   * @since 4.7.0
3463   *
3464   * @see wp_insert_post()
3465   *
3466   * @param array $post_data          An array of slashed post data.
3467   * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
3468   * @return array Filtered data.
3469   */
3470  function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
3471      if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
3472  
3473          // Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
3474          if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
3475              $post_data['post_name'] = $supplied_post_data['post_name'];
3476          }
3477      }
3478      return $post_data;
3479  }
3480  
3481  /**
3482   * Adds settings for the customize-loader script.
3483   *
3484   * @since 3.4.0
3485   */
3486  function _wp_customize_loader_settings() {
3487      $admin_origin = parse_url( admin_url() );
3488      $home_origin  = parse_url( home_url() );
3489      $cross_domain = ( strtolower( $admin_origin['host'] ) != strtolower( $home_origin['host'] ) );
3490  
3491      $browser = array(
3492          'mobile' => wp_is_mobile(),
3493          'ios'    => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
3494      );
3495  
3496      $settings = array(
3497          'url'           => esc_url( admin_url( 'customize.php' ) ),
3498          'isCrossDomain' => $cross_domain,
3499          'browser'       => $browser,
3500          'l10n'          => array(
3501              'saveAlert'       => __( 'The changes you made will be lost if you navigate away from this page.' ),
3502              'mainIframeTitle' => __( 'Customizer' ),
3503          ),
3504      );
3505  
3506      $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
3507  
3508      $wp_scripts = wp_scripts();
3509      $data       = $wp_scripts->get_data( 'customize-loader', 'data' );
3510      if ( $data ) {
3511          $script = "$data\n$script";
3512      }
3513  
3514      $wp_scripts->add_data( 'customize-loader', 'data', $script );
3515  }
3516  
3517  /**
3518   * Returns a URL to load the Customizer.
3519   *
3520   * @since 3.4.0
3521   *
3522   * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
3523   *                           The theme's stylesheet will be urlencoded if necessary.
3524   * @return string
3525   */
3526  function wp_customize_url( $stylesheet = '' ) {
3527      $url = admin_url( 'customize.php' );
3528      if ( $stylesheet ) {
3529          $url .= '?theme=' . urlencode( $stylesheet );
3530      }
3531      return esc_url( $url );
3532  }
3533  
3534  /**
3535   * Prints a script to check whether or not the Customizer is supported,
3536   * and apply either the no-customize-support or customize-support class
3537   * to the body.
3538   *
3539   * This function MUST be called inside the body tag.
3540   *
3541   * Ideally, call this function immediately after the body tag is opened.
3542   * This prevents a flash of unstyled content.
3543   *
3544   * It is also recommended that you add the "no-customize-support" class
3545   * to the body tag by default.
3546   *
3547   * @since 3.4.0
3548   * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
3549   * @since 5.5.0 IE8 and older are no longer supported.
3550   */
3551  function wp_customize_support_script() {
3552      $admin_origin = parse_url( admin_url() );
3553      $home_origin  = parse_url( home_url() );
3554      $cross_domain = ( strtolower( $admin_origin['host'] ) != strtolower( $home_origin['host'] ) );
3555      $type_attr    = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
3556      ?>
3557      <script<?php echo $type_attr; ?>>
3558          (function() {
3559              var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
3560  
3561      <?php    if ( $cross_domain ) : ?>
3562              request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
3563      <?php    else : ?>
3564              request = true;
3565      <?php    endif; ?>
3566  
3567              b[c] = b[c].replace( rcs, ' ' );
3568              // The customizer requires postMessage and CORS (if the site is cross domain).
3569              b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
3570          }());
3571      </script>
3572      <?php
3573  }
3574  
3575  /**
3576   * Whether the site is being previewed in the Customizer.
3577   *
3578   * @since 4.0.0
3579   *
3580   * @global WP_Customize_Manager $wp_customize Customizer instance.
3581   *
3582   * @return bool True if the site is being previewed in the Customizer, false otherwise.
3583   */
3584  function is_customize_preview() {
3585      global $wp_customize;
3586  
3587      return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();
3588  }
3589  
3590  /**
3591   * Makes sure that auto-draft posts get their post_date bumped or status changed to draft to prevent premature garbage-collection.
3592   *
3593   * When a changeset is updated but remains an auto-draft, ensure the post_date
3594   * for the auto-draft posts remains the same so that it will be
3595   * garbage-collected at the same time by `wp_delete_auto_drafts()`. Otherwise,
3596   * if the changeset is updated to be a draft then update the posts
3597   * to have a far-future post_date so that they will never be garbage collected
3598   * unless the changeset post itself is deleted.
3599   *
3600   * When a changeset is updated to be a persistent draft or to be scheduled for
3601   * publishing, then transition any dependent auto-drafts to a draft status so
3602   * that they likewise will not be garbage-collected but also so that they can
3603   * be edited in the admin before publishing since there is not yet a post/page
3604   * editing flow in the Customizer. See #39752.
3605   *
3606   * @link https://core.trac.wordpress.org/ticket/39752
3607   *
3608   * @since 4.8.0
3609   * @access private
3610   * @see wp_delete_auto_drafts()
3611   *
3612   * @global wpdb $wpdb WordPress database abstraction object.
3613   *
3614   * @param string   $new_status Transition to this post status.
3615   * @param string   $old_status Previous post status.
3616   * @param \WP_Post $post       Post data.
3617   */
3618  function _wp_keep_alive_customize_changeset_dependent_auto_drafts( $new_status, $old_status, $post ) {
3619      global $wpdb;
3620      unset( $old_status );
3621  
3622      // Short-circuit if not a changeset or if the changeset was published.
3623      if ( 'customize_changeset' !== $post->post_type || 'publish' === $new_status ) {
3624          return;
3625      }
3626  
3627      $data = json_decode( $post->post_content, true );
3628      if ( empty( $data['nav_menus_created_posts']['value'] ) ) {
3629          return;
3630      }
3631  
3632      /*
3633       * Actually, in lieu of keeping alive, trash any customization drafts here if the changeset itself is
3634       * getting trashed. This is needed because when a changeset transitions to a draft, then any of the
3635       * dependent auto-draft post/page stubs will also get transitioned to customization drafts which
3636       * are then visible in the WP Admin. We cannot wait for the deletion of the changeset in which
3637       * _wp_delete_customize_changeset_dependent_auto_drafts() will be called, since they need to be
3638       * trashed to remove from visibility immediately.
3639       */
3640      if ( 'trash' === $new_status ) {
3641          foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
3642              if ( ! empty( $post_id ) && 'draft' === get_post_status( $post_id ) ) {
3643                  wp_trash_post( $post_id );
3644              }
3645          }
3646          return;
3647      }
3648  
3649      $post_args = array();
3650      if ( 'auto-draft' === $new_status ) {
3651          /*
3652           * Keep the post date for the post matching the changeset
3653           * so that it will not be garbage-collected before the changeset.
3654           */
3655          $post_args['post_date'] = $post->post_date; // Note wp_delete_auto_drafts() only looks at this date.
3656      } else {
3657          /*
3658           * Since the changeset no longer has an auto-draft (and it is not published)
3659           * it is now a persistent changeset, a long-lived draft, and so any
3660           * associated auto-draft posts should likewise transition into having a draft
3661           * status. These drafts will be treated differently than regular drafts in
3662           * that they will be tied to the given changeset. The publish meta box is
3663           * replaced with a notice about how the post is part of a set of customized changes
3664           * which will be published when the changeset is published.
3665           */
3666          $post_args['post_status'] = 'draft';
3667      }
3668  
3669      foreach ( $data['nav_menus_created_posts']['value'] as $post_id ) {
3670          if ( empty( $post_id ) || 'auto-draft' !== get_post_status( $post_id ) ) {
3671              continue;
3672          }
3673          $wpdb->update(
3674              $wpdb->posts,
3675              $post_args,
3676              array( 'ID' => $post_id )
3677          );
3678          clean_post_cache( $post_id );
3679      }
3680  }
3681  
3682  /**
3683   * Creates the initial theme features when the 'setup_theme' action is fired.
3684   *
3685   * See {@see 'setup_theme'}.
3686   *
3687   * @since 5.5.0
3688   */
3689  function create_initial_theme_features() {
3690      register_theme_feature(
3691          'align-wide',
3692          array(
3693              'description'  => __( 'Whether theme opts in to wide alignment CSS class.' ),
3694              'show_in_rest' => true,
3695          )
3696      );
3697      register_theme_feature(
3698          'automatic-feed-links',
3699          array(
3700              'description'  => __( 'Whether posts and comments RSS feed links are added to head.' ),
3701              'show_in_rest' => true,
3702          )
3703      );
3704      register_theme_feature(
3705          'custom-background',
3706          array(
3707              'description'  => __( 'Custom background if defined by the theme.' ),
3708              'type'         => 'object',
3709              'show_in_rest' => array(
3710                  'schema' => array(
3711                      'properties' => array(
3712                          'default-image'      => array(
3713                              'type'   => 'string',
3714                              'format' => 'uri',
3715                          ),
3716                          'default-preset'     => array(
3717                              'type' => 'string',
3718                              'enum' => array(
3719                                  'default',
3720                                  'fill',
3721                                  'fit',
3722                                  'repeat',
3723                                  'custom',
3724                              ),
3725                          ),
3726                          'default-position-x' => array(
3727                              'type' => 'string',
3728                              'enum' => array(
3729                                  'left',
3730                                  'center',
3731                                  'right',
3732                              ),
3733                          ),
3734                          'default-position-y' => array(
3735                              'type' => 'string',
3736                              'enum' => array(
3737                                  'left',
3738                                  'center',
3739                                  'right',
3740                              ),
3741                          ),
3742                          'default-size'       => array(
3743                              'type' => 'string',
3744                              'enum' => array(
3745                                  'auto',
3746                                  'contain',
3747                                  'cover',
3748                              ),
3749                          ),
3750                          'default-repeat'     => array(
3751                              'type' => 'string',
3752                              'enum' => array(
3753                                  'repeat-x',
3754                                  'repeat-y',
3755                                  'repeat',
3756                                  'no-repeat',
3757                              ),
3758                          ),
3759                          'default-attachment' => array(
3760                              'type' => 'string',
3761                              'enum' => array(
3762                                  'scroll',
3763                                  'fixed',
3764                              ),
3765                          ),
3766                          'default-color'      => array(
3767                              'type' => 'string',
3768                          ),
3769                      ),
3770                  ),
3771              ),
3772          )
3773      );
3774      register_theme_feature(
3775          'custom-header',
3776          array(
3777              'description'  => __( 'Custom header if defined by the theme.' ),
3778              'type'         => 'object',
3779              'show_in_rest' => array(
3780                  'schema' => array(
3781                      'properties' => array(
3782                          'default-image'      => array(
3783                              'type'   => 'string',
3784                              'format' => 'uri',
3785                          ),
3786                          'random-default'     => array(
3787                              'type' => 'boolean',
3788                          ),
3789                          'width'              => array(
3790                              'type' => 'integer',
3791                          ),
3792                          'height'             => array(
3793                              'type' => 'integer',
3794                          ),
3795                          'flex-height'        => array(
3796                              'type' => 'boolean',
3797                          ),
3798                          'flex-width'         => array(
3799                              'type' => 'boolean',
3800                          ),
3801                          'default-text-color' => array(
3802                              'type' => 'string',
3803                          ),
3804                          'header-text'        => array(
3805                              'type' => 'boolean',
3806                          ),
3807                          'uploads'            => array(
3808                              'type' => 'boolean',
3809                          ),
3810                          'video'              => array(
3811                              'type' => 'boolean',
3812                          ),
3813                      ),
3814                  ),
3815              ),
3816          )
3817      );
3818      register_theme_feature(
3819          'custom-logo',
3820          array(
3821              'type'         => 'object',
3822              'description'  => __( 'Custom logo if defined by the theme.' ),
3823              'show_in_rest' => array(
3824                  'schema' => array(
3825                      'properties' => array(
3826                          'width'                => array(
3827                              'type' => 'integer',
3828                          ),
3829                          'height'               => array(
3830                              'type' => 'integer',
3831                          ),
3832                          'flex-width'           => array(
3833                              'type' => 'boolean',
3834                          ),
3835                          'flex-height'          => array(
3836                              'type' => 'boolean',
3837                          ),
3838                          'header-text'          => array(
3839                              'type'  => 'array',
3840                              'items' => array(
3841                                  'type' => 'string',
3842                              ),
3843                          ),
3844                          'unlink-homepage-logo' => array(
3845                              'type' => 'boolean',
3846                          ),
3847                      ),
3848                  ),
3849              ),
3850          )
3851      );
3852      register_theme_feature(
3853          'customize-selective-refresh-widgets',
3854          array(
3855              'description'  => __( 'Whether the theme enables Selective Refresh for Widgets being managed with the Customizer.' ),
3856              'show_in_rest' => true,
3857          )
3858      );
3859      register_theme_feature(
3860          'dark-editor-style',
3861          array(
3862              'description'  => __( 'Whether theme opts in to the dark editor style UI.' ),
3863              'show_in_rest' => true,
3864          )
3865      );
3866      register_theme_feature(
3867          'disable-custom-colors',
3868          array(
3869              'description'  => __( 'Whether the theme disables custom colors.' ),
3870              'show_in_rest' => true,
3871          )
3872      );
3873      register_theme_feature(
3874          'disable-custom-font-sizes',
3875          array(
3876              'description'  => __( 'Whether the theme disables custom font sizes.' ),
3877              'show_in_rest' => true,
3878          )
3879      );
3880      register_theme_feature(
3881          'disable-custom-gradients',
3882          array(
3883              'description'  => __( 'Whether the theme disables custom gradients.' ),
3884              'show_in_rest' => true,
3885          )
3886      );
3887      register_theme_feature(
3888          'editor-color-palette',
3889          array(
3890              'type'         => 'array',
3891              'description'  => __( 'Custom color palette if defined by the theme.' ),
3892              'show_in_rest' => array(
3893                  'schema' => array(
3894                      'items' => array(
3895                          'type'       => 'object',
3896                          'properties' => array(
3897                              'name'  => array(
3898                                  'type' => 'string',
3899                              ),
3900                              'slug'  => array(
3901                                  'type' => 'string',
3902                              ),
3903                              'color' => array(
3904                                  'type' => 'string',
3905                              ),
3906                          ),
3907                      ),
3908                  ),
3909              ),
3910          )
3911      );
3912      register_theme_feature(
3913          'editor-font-sizes',
3914          array(
3915              'type'         => 'array',
3916              'description'  => __( 'Custom font sizes if defined by the theme.' ),
3917              'show_in_rest' => array(
3918                  'schema' => array(
3919                      'items' => array(
3920                          'type'       => 'object',
3921                          'properties' => array(
3922                              'name' => array(
3923                                  'type' => 'string',
3924                              ),
3925                              'size' => array(
3926                                  'type' => 'number',
3927                              ),
3928                              'slug' => array(
3929                                  'type' => 'string',
3930                              ),
3931                          ),
3932                      ),
3933                  ),
3934              ),
3935          )
3936      );
3937      register_theme_feature(
3938          'editor-gradient-presets',
3939          array(
3940              'type'         => 'array',
3941              'description'  => __( 'Custom gradient presets if defined by the theme.' ),
3942              'show_in_rest' => array(
3943                  'schema' => array(
3944                      'items' => array(
3945                          'type'       => 'object',
3946                          'properties' => array(
3947                              'name'     => array(
3948                                  'type' => 'string',
3949                              ),
3950                              'gradient' => array(
3951                                  'type' => 'string',
3952                              ),
3953                              'slug'     => array(
3954                                  'type' => 'string',
3955                              ),
3956                          ),
3957                      ),
3958                  ),
3959              ),
3960          )
3961      );
3962      register_theme_feature(
3963          'editor-styles',
3964          array(
3965              'description'  => __( 'Whether theme opts in to the editor styles CSS wrapper.' ),
3966              'show_in_rest' => true,
3967          )
3968      );
3969      register_theme_feature(
3970          'html5',
3971          array(
3972              'type'         => 'array',
3973              'description'  => __( 'Allows use of HTML5 markup for search forms, comment forms, comment lists, gallery, and caption.' ),
3974              'show_in_rest' => array(
3975                  'schema' => array(
3976                      'items' => array(
3977                          'type' => 'string',
3978                          'enum' => array(
3979                              'search-form',
3980                              'comment-form',
3981                              'comment-list',
3982                              'gallery',
3983                              'caption',
3984                              'script',
3985                              'style',
3986                          ),
3987                      ),
3988                  ),
3989              ),
3990          )
3991      );
3992      register_theme_feature(
3993          'post-formats',
3994          array(
3995              'type'         => 'array',
3996              'description'  => __( 'Post formats supported.' ),
3997              'show_in_rest' => array(
3998                  'name'             => 'formats',
3999                  'schema'           => array(
4000                      'items'   => array(
4001                          'type' => 'string',
4002                          'enum' => get_post_format_slugs(),
4003                      ),
4004                      'default' => array( 'standard' ),
4005                  ),
4006                  'prepare_callback' => static function ( $formats ) {
4007                      $formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
4008                      $formats = array_merge( array( 'standard' ), $formats );
4009  
4010                      return $formats;
4011                  },
4012              ),
4013          )
4014      );
4015      register_theme_feature(
4016          'post-thumbnails',
4017          array(
4018              'type'         => 'array',
4019              'description'  => __( 'The post types that support thumbnails or true if all post types are supported.' ),
4020              'show_in_rest' => array(
4021                  'type'   => array( 'boolean', 'array' ),
4022                  'schema' => array(
4023                      'items' => array(
4024                          'type' => 'string',
4025                      ),
4026                  ),
4027              ),
4028          )
4029      );
4030      register_theme_feature(
4031          'responsive-embeds',
4032          array(
4033              'description'  => __( 'Whether the theme supports responsive embedded content.' ),
4034              'show_in_rest' => true,
4035          )
4036      );
4037      register_theme_feature(
4038          'title-tag',
4039          array(
4040              'description'  => __( 'Whether the theme can manage the document title tag.' ),
4041              'show_in_rest' => true,
4042          )
4043      );
4044      register_theme_feature(
4045          'wp-block-styles',
4046          array(
4047              'description'  => __( 'Whether theme opts in to default WordPress block styles for viewing.' ),
4048              'show_in_rest' => true,
4049          )
4050      );
4051  }


Generated: Fri Oct 30 01:00:03 2020 Cross-referenced by PHPXref 0.7.1