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


Generated : Sat Jun 6 08:20:01 2020 Cross-referenced by PHPXref