[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref