[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Tue Nov 19 01:00:03 2019 Cross-referenced by PHPXref 0.7.1