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