home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / theme.php < prev    next >
Encoding:
PHP Script  |  2017-10-24  |  97.4 KB  |  3,144 lines

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