WordPress 4.7.2-scripts
[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("' . esc_url_raw( $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_post_term_cache' => false,
1647                 'lazy_load_term_meta'    => false,
1648         );
1649
1650         $post = null;
1651         if ( get_stylesheet() === $stylesheet ) {
1652                 $post_id = get_theme_mod( 'custom_css_post_id' );
1653
1654                 if ( $post_id > 0 && get_post( $post_id ) ) {
1655                         $post = get_post( $post_id );
1656                 }
1657
1658                 // `-1` indicates no post exists; no query necessary.
1659                 if ( ! $post && -1 !== $post_id ) {
1660                         $query = new WP_Query( $custom_css_query_vars );
1661                         $post = $query->post;
1662                         /*
1663                          * Cache the lookup. See wp_update_custom_css_post().
1664                          * @todo This should get cleared if a custom_css post is added/removed.
1665                          */
1666                         set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
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                 if ( ! is_wp_error( $r ) ) {
1791                         if ( get_stylesheet() === $args['stylesheet'] ) {
1792                                 set_theme_mod( 'custom_css_post_id', $r );
1793                         }
1794
1795                         // Trigger creation of a revision. This should be removed once #30854 is resolved.
1796                         if ( 0 === count( wp_get_post_revisions( $r ) ) ) {
1797                                 wp_save_post_revision( $r );
1798                         }
1799                 }
1800         }
1801
1802         if ( is_wp_error( $r ) ) {
1803                 return $r;
1804         }
1805         return get_post( $r );
1806 }
1807
1808 /**
1809  * Add callback for custom TinyMCE editor stylesheets.
1810  *
1811  * The parameter $stylesheet is the name of the stylesheet, relative to
1812  * the theme root. It also accepts an array of stylesheets.
1813  * It is optional and defaults to 'editor-style.css'.
1814  *
1815  * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
1816  * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
1817  * If an array of stylesheets is passed to add_editor_style(),
1818  * RTL is only added for the first stylesheet.
1819  *
1820  * Since version 3.4 the TinyMCE body has .rtl CSS class.
1821  * It is a better option to use that class and add any RTL styles to the main stylesheet.
1822  *
1823  * @since 3.0.0
1824  *
1825  * @global array $editor_styles
1826  *
1827  * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
1828  *                                     Defaults to 'editor-style.css'
1829  */
1830 function add_editor_style( $stylesheet = 'editor-style.css' ) {
1831         add_theme_support( 'editor-style' );
1832
1833         if ( ! is_admin() )
1834                 return;
1835
1836         global $editor_styles;
1837         $editor_styles = (array) $editor_styles;
1838         $stylesheet    = (array) $stylesheet;
1839         if ( is_rtl() ) {
1840                 $rtl_stylesheet = str_replace('.css', '-rtl.css', $stylesheet[0]);
1841                 $stylesheet[] = $rtl_stylesheet;
1842         }
1843
1844         $editor_styles = array_merge( $editor_styles, $stylesheet );
1845 }
1846
1847 /**
1848  * Removes all visual editor stylesheets.
1849  *
1850  * @since 3.1.0
1851  *
1852  * @global array $editor_styles
1853  *
1854  * @return bool True on success, false if there were no stylesheets to remove.
1855  */
1856 function remove_editor_styles() {
1857         if ( ! current_theme_supports( 'editor-style' ) )
1858                 return false;
1859         _remove_theme_support( 'editor-style' );
1860         if ( is_admin() )
1861                 $GLOBALS['editor_styles'] = array();
1862         return true;
1863 }
1864
1865 /**
1866  * Retrieve any registered editor stylesheets
1867  *
1868  * @since 4.0.0
1869  *
1870  * @global array $editor_styles Registered editor stylesheets
1871  *
1872  * @return array If registered, a list of editor stylesheet URLs.
1873  */
1874 function get_editor_stylesheets() {
1875         $stylesheets = array();
1876         // load editor_style.css if the current theme supports it
1877         if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
1878                 $editor_styles = $GLOBALS['editor_styles'];
1879
1880                 $editor_styles = array_unique( array_filter( $editor_styles ) );
1881                 $style_uri = get_stylesheet_directory_uri();
1882                 $style_dir = get_stylesheet_directory();
1883
1884                 // Support externally referenced styles (like, say, fonts).
1885                 foreach ( $editor_styles as $key => $file ) {
1886                         if ( preg_match( '~^(https?:)?//~', $file ) ) {
1887                                 $stylesheets[] = esc_url_raw( $file );
1888                                 unset( $editor_styles[ $key ] );
1889                         }
1890                 }
1891
1892                 // Look in a parent theme first, that way child theme CSS overrides.
1893                 if ( is_child_theme() ) {
1894                         $template_uri = get_template_directory_uri();
1895                         $template_dir = get_template_directory();
1896
1897                         foreach ( $editor_styles as $key => $file ) {
1898                                 if ( $file && file_exists( "$template_dir/$file" ) ) {
1899                                         $stylesheets[] = "$template_uri/$file";
1900                                 }
1901                         }
1902                 }
1903
1904                 foreach ( $editor_styles as $file ) {
1905                         if ( $file && file_exists( "$style_dir/$file" ) ) {
1906                                 $stylesheets[] = "$style_uri/$file";
1907                         }
1908                 }
1909         }
1910
1911         /**
1912          * Filters the array of stylesheets applied to the editor.
1913          *
1914          * @since 4.3.0
1915          *
1916          * @param array $stylesheets Array of stylesheets to be applied to the editor.
1917          */
1918         return apply_filters( 'editor_stylesheets', $stylesheets );
1919 }
1920
1921 /**
1922  * Expand a theme's starter content configuration using core-provided data.
1923  *
1924  * @since 4.7.0
1925  *
1926  * @return array Array of starter content.
1927  */
1928 function get_theme_starter_content() {
1929         $theme_support = get_theme_support( 'starter-content' );
1930         if ( is_array( $theme_support ) && ! empty( $theme_support[0] ) && is_array( $theme_support[0] ) ) {
1931                 $config = $theme_support[0];
1932         } else {
1933                 $config = array();
1934         }
1935
1936         $core_content = array(
1937                 'widgets' => array(
1938                         'text_business_info' => array( 'text', array(
1939                                 'title' => _x( 'Find Us', 'Theme starter content' ),
1940                                 'text' => join( '', array(
1941                                         '<p><strong>' . _x( 'Address', 'Theme starter content' ) . '</strong><br />',
1942                                         _x( '123 Main Street', 'Theme starter content' ) . '<br />' . _x( 'New York, NY 10001', 'Theme starter content' ) . '</p>',
1943                                         '<p><strong>' . _x( 'Hours', 'Theme starter content' ) . '</strong><br />',
1944                                         _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>'
1945                                 ) ),
1946                         ) ),
1947                         'text_about' => array( 'text', array(
1948                                 'title' => _x( 'About This Site', 'Theme starter content' ),
1949                                 'text' => _x( 'This may be a good place to introduce yourself and your site or include some credits.', 'Theme starter content' ),
1950                         ) ),
1951                         'archives' => array( 'archives', array(
1952                                 'title' => _x( 'Archives', 'Theme starter content' ),
1953                         ) ),
1954                         'calendar' => array( 'calendar', array(
1955                                 'title' => _x( 'Calendar', 'Theme starter content' ),
1956                         ) ),
1957                         'categories' => array( 'categories', array(
1958                                 'title' => _x( 'Categories', 'Theme starter content' ),
1959                         ) ),
1960                         'meta' => array( 'meta', array(
1961                                 'title' => _x( 'Meta', 'Theme starter content' ),
1962                         ) ),
1963                         'recent-comments' => array( 'recent-comments', array(
1964                                 'title' => _x( 'Recent Comments', 'Theme starter content' ),
1965                         ) ),
1966                         'recent-posts' => array( 'recent-posts', array(
1967                                 'title' => _x( 'Recent Posts', 'Theme starter content' ),
1968                         ) ),
1969                         'search' => array( 'search', array(
1970                                 'title' => _x( 'Search', 'Theme starter content' ),
1971                         ) ),
1972                 ),
1973                 'nav_menus' => array(
1974                         'link_home' => array(
1975                                 'type' => 'custom',
1976                                 'title' => _x( 'Home', 'Theme starter content' ),
1977                                 'url' => home_url(),
1978                         ),
1979                         'page_home' => array( // Deprecated in favor of home_link.
1980                                 'type' => 'post_type',
1981                                 'object' => 'page',
1982                                 'object_id' => '{{home}}',
1983                         ),
1984                         'page_about' => array(
1985                                 'type' => 'post_type',
1986                                 'object' => 'page',
1987                                 'object_id' => '{{about}}',
1988                         ),
1989                         'page_blog' => array(
1990                                 'type' => 'post_type',
1991                                 'object' => 'page',
1992                                 'object_id' => '{{blog}}',
1993                         ),
1994                         'page_news' => array(
1995                                 'type' => 'post_type',
1996                                 'object' => 'page',
1997                                 'object_id' => '{{news}}',
1998                         ),
1999                         'page_contact' => array(
2000                                 'type' => 'post_type',
2001                                 'object' => 'page',
2002                                 'object_id' => '{{contact}}',
2003                         ),
2004
2005                         'link_email' => array(
2006                                 'title' => _x( 'Email', 'Theme starter content' ),
2007                                 'url' => 'mailto:wordpress@example.com',
2008                         ),
2009                         'link_facebook' => array(
2010                                 'title' => _x( 'Facebook', 'Theme starter content' ),
2011                                 'url' => 'https://www.facebook.com/wordpress',
2012                         ),
2013                         'link_foursquare' => array(
2014                                 'title' => _x( 'Foursquare', 'Theme starter content' ),
2015                                 'url' => 'https://foursquare.com/',
2016                         ),
2017                         'link_github' => array(
2018                                 'title' => _x( 'GitHub', 'Theme starter content' ),
2019                                 'url' => 'https://github.com/wordpress/',
2020                         ),
2021                         'link_instagram' => array(
2022                                 'title' => _x( 'Instagram', 'Theme starter content' ),
2023                                 'url' => 'https://www.instagram.com/explore/tags/wordcamp/',
2024                         ),
2025                         'link_linkedin' => array(
2026                                 'title' => _x( 'LinkedIn', 'Theme starter content' ),
2027                                 'url' => 'https://www.linkedin.com/company/1089783',
2028                         ),
2029                         'link_pinterest' => array(
2030                                 'title' => _x( 'Pinterest', 'Theme starter content' ),
2031                                 'url' => 'https://www.pinterest.com/',
2032                         ),
2033                         'link_twitter' => array(
2034                                 'title' => _x( 'Twitter', 'Theme starter content' ),
2035                                 'url' => 'https://twitter.com/wordpress',
2036                         ),
2037                         'link_yelp' => array(
2038                                 'title' => _x( 'Yelp', 'Theme starter content' ),
2039                                 'url' => 'https://www.yelp.com',
2040                         ),
2041                         'link_youtube' => array(
2042                                 'title' => _x( 'YouTube', 'Theme starter content' ),
2043                                 'url' => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
2044                         ),
2045                 ),
2046                 'posts' => array(
2047                         'home' => array(
2048                                 'post_type' => 'page',
2049                                 'post_title' => _x( 'Home', 'Theme starter content' ),
2050                                 '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' ),
2051                         ),
2052                         'about' => array(
2053                                 'post_type' => 'page',
2054                                 'post_title' => _x( 'About', 'Theme starter content' ),
2055                                 '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' ),
2056                         ),
2057                         'contact' => array(
2058                                 'post_type' => 'page',
2059                                 'post_title' => _x( 'Contact', 'Theme starter content' ),
2060                                 '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' ),
2061                         ),
2062                         'blog' => array(
2063                                 'post_type' => 'page',
2064                                 'post_title' => _x( 'Blog', 'Theme starter content' ),
2065                         ),
2066                         'news' => array(
2067                                 'post_type' => 'page',
2068                                 'post_title' => _x( 'News', 'Theme starter content' ),
2069                         ),
2070
2071                         'homepage-section' => array(
2072                                 'post_type' => 'page',
2073                                 'post_title' => _x( 'A homepage section', 'Theme starter content' ),
2074                                 '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' ),
2075                         ),
2076                 ),
2077         );
2078
2079         $content = array();
2080
2081         foreach ( $config as $type => $args ) {
2082                 switch( $type ) {
2083                         // Use options and theme_mods as-is.
2084                         case 'options' :
2085                         case 'theme_mods' :
2086                                 $content[ $type ] = $config[ $type ];
2087                                 break;
2088
2089                         // Widgets are grouped into sidebars.
2090                         case 'widgets' :
2091                                 foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
2092                                         foreach ( $widgets as $id => $widget ) {
2093                                                 if ( is_array( $widget ) ) {
2094
2095                                                         // Item extends core content.
2096                                                         if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2097                                                                 $widget = array(
2098                                                                         $core_content[ $type ][ $id ][0],
2099                                                                         array_merge( $core_content[ $type ][ $id ][1], $widget ),
2100                                                                 );
2101                                                         }
2102
2103                                                         $content[ $type ][ $sidebar_id ][] = $widget;
2104                                                 } elseif ( is_string( $widget ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $widget ] ) ) {
2105                                                         $content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
2106                                                 }
2107                                         }
2108                                 }
2109                                 break;
2110
2111                         // And nav menu items are grouped into nav menus.
2112                         case 'nav_menus' :
2113                                 foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
2114
2115                                         // Ensure nav menus get a name.
2116                                         if ( empty( $nav_menu['name'] ) ) {
2117                                                 $nav_menu['name'] = $nav_menu_location;
2118                                         }
2119
2120                                         $content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
2121
2122                                         foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
2123                                                 if ( is_array( $nav_menu_item ) ) {
2124
2125                                                         // Item extends core content.
2126                                                         if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2127                                                                 $nav_menu_item = array_merge( $core_content[ $type ][ $id ], $nav_menu_item );
2128                                                         }
2129
2130                                                         $content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
2131                                                 } elseif ( is_string( $nav_menu_item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $nav_menu_item ] ) ) {
2132                                                         $content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
2133                                                 }
2134                                         }
2135                                 }
2136                                 break;
2137
2138                         // Attachments are posts but have special treatment.
2139                         case 'attachments' :
2140                                 foreach ( $config[ $type ] as $id => $item ) {
2141                                         if ( ! empty( $item['file'] ) ) {
2142                                                 $content[ $type ][ $id ] = $item;
2143                                         }
2144                                 }
2145                                 break;
2146
2147                         // All that's left now are posts (besides attachments). Not a default case for the sake of clarity and future work.
2148                         case 'posts' :
2149                                 foreach ( $config[ $type ] as $id => $item ) {
2150                                         if ( is_array( $item ) ) {
2151
2152                                                 // Item extends core content.
2153                                                 if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2154                                                         $item = array_merge( $core_content[ $type ][ $id ], $item );
2155                                                 }
2156
2157                                                 // Enforce a subset of fields.
2158                                                 $content[ $type ][ $id ] = wp_array_slice_assoc(
2159                                                         $item,
2160                                                         array(
2161                                                                 'post_type',
2162                                                                 'post_title',
2163                                                                 'post_excerpt',
2164                                                                 'post_name',
2165                                                                 'post_content',
2166                                                                 'menu_order',
2167                                                                 'comment_status',
2168                                                                 'thumbnail',
2169                                                                 'template',
2170                                                         )
2171                                                 );
2172                                         } elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
2173                                                 $content[ $type ][ $item ] = $core_content[ $type ][ $item ];
2174                                         }
2175                                 }
2176                                 break;
2177                 }
2178         }
2179
2180         /**
2181          * Filters the expanded array of starter content.
2182          *
2183          * @since 4.7.0
2184          *
2185          * @param array $content Array of starter content.
2186          * @param array $config  Array of theme-specific starter content configuration.
2187          */
2188         return apply_filters( 'get_theme_starter_content', $content, $config );
2189 }
2190
2191 /**
2192  * Registers theme support for a given feature.
2193  *
2194  * Must be called in the theme's functions.php file to work.
2195  * If attached to a hook, it must be {@see 'after_setup_theme'}.
2196  * The {@see 'init'} hook may be too late for some features.
2197  *
2198  * @since 2.9.0
2199  * @since 3.6.0 The `html5` feature was added
2200  * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'
2201  * @since 4.1.0 The `title-tag` feature was added
2202  * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added
2203  * @since 4.7.0 The `starter-content` feature was added
2204  *
2205  * @global array $_wp_theme_features
2206  *
2207  * @param string $feature  The feature being added. Likely core values include 'post-formats',
2208  *                         'post-thumbnails', 'html5', 'custom-logo', 'custom-header-uploads',
2209  *                         'custom-header', 'custom-background', 'title-tag', 'starter-content', etc.
2210  * @param mixed  $args,... Optional extra arguments to pass along with certain features.
2211  * @return void|bool False on failure, void otherwise.
2212  */
2213 function add_theme_support( $feature ) {
2214         global $_wp_theme_features;
2215
2216         if ( func_num_args() == 1 )
2217                 $args = true;
2218         else
2219                 $args = array_slice( func_get_args(), 1 );
2220
2221         switch ( $feature ) {
2222                 case 'post-thumbnails':
2223                         // All post types are already supported.
2224                         if ( true === get_theme_support( 'post-thumbnails' ) ) {
2225                                 return;
2226                         }
2227
2228                         /*
2229                          * Merge post types with any that already declared their support
2230                          * for post thumbnails.
2231                          */
2232                         if ( is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
2233                                 $args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
2234                         }
2235
2236                         break;
2237
2238                 case 'post-formats' :
2239                         if ( is_array( $args[0] ) ) {
2240                                 $post_formats = get_post_format_slugs();
2241                                 unset( $post_formats['standard'] );
2242
2243                                 $args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
2244                         }
2245                         break;
2246
2247                 case 'html5' :
2248                         // You can't just pass 'html5', you need to pass an array of types.
2249                         if ( empty( $args[0] ) ) {
2250                                 // Build an array of types for back-compat.
2251                                 $args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
2252                         } elseif ( ! is_array( $args[0] ) ) {
2253                                 _doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
2254                                 return false;
2255                         }
2256
2257                         // Calling 'html5' again merges, rather than overwrites.
2258                         if ( isset( $_wp_theme_features['html5'] ) )
2259                                 $args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
2260                         break;
2261
2262                 case 'custom-logo':
2263                         if ( ! is_array( $args ) ) {
2264                                 $args = array( 0 => array() );
2265                         }
2266                         $defaults = array(
2267                                 'width'       => null,
2268                                 'height'      => null,
2269                                 'flex-width'  => false,
2270                                 'flex-height' => false,
2271                                 'header-text' => '',
2272                         );
2273                         $args[0] = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
2274
2275                         // Allow full flexibility if no size is specified.
2276                         if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
2277                                 $args[0]['flex-width']  = true;
2278                                 $args[0]['flex-height'] = true;
2279                         }
2280                         break;
2281
2282                 case 'custom-header-uploads' :
2283                         return add_theme_support( 'custom-header', array( 'uploads' => true ) );
2284
2285                 case 'custom-header' :
2286                         if ( ! is_array( $args ) )
2287                                 $args = array( 0 => array() );
2288
2289                         $defaults = array(
2290                                 'default-image' => '',
2291                                 'random-default' => false,
2292                                 'width' => 0,
2293                                 'height' => 0,
2294                                 'flex-height' => false,
2295                                 'flex-width' => false,
2296                                 'default-text-color' => '',
2297                                 'header-text' => true,
2298                                 'uploads' => true,
2299                                 'wp-head-callback' => '',
2300                                 'admin-head-callback' => '',
2301                                 'admin-preview-callback' => '',
2302                                 'video' => false,
2303                                 'video-active-callback' => 'is_front_page',
2304                         );
2305
2306                         $jit = isset( $args[0]['__jit'] );
2307                         unset( $args[0]['__jit'] );
2308
2309                         // Merge in data from previous add_theme_support() calls.
2310                         // The first value registered wins. (A child theme is set up first.)
2311                         if ( isset( $_wp_theme_features['custom-header'] ) )
2312                                 $args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
2313
2314                         // Load in the defaults at the end, as we need to insure first one wins.
2315                         // This will cause all constants to be defined, as each arg will then be set to the default.
2316                         if ( $jit )
2317                                 $args[0] = wp_parse_args( $args[0], $defaults );
2318
2319                         // If a constant was defined, use that value. Otherwise, define the constant to ensure
2320                         // the constant is always accurate (and is not defined later,  overriding our value).
2321                         // As stated above, the first value wins.
2322                         // Once we get to wp_loaded (just-in-time), define any constants we haven't already.
2323                         // Constants are lame. Don't reference them. This is just for backward compatibility.
2324
2325                         if ( defined( 'NO_HEADER_TEXT' ) )
2326                                 $args[0]['header-text'] = ! NO_HEADER_TEXT;
2327                         elseif ( isset( $args[0]['header-text'] ) )
2328                                 define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
2329
2330                         if ( defined( 'HEADER_IMAGE_WIDTH' ) )
2331                                 $args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
2332                         elseif ( isset( $args[0]['width'] ) )
2333                                 define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
2334
2335                         if ( defined( 'HEADER_IMAGE_HEIGHT' ) )
2336                                 $args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
2337                         elseif ( isset( $args[0]['height'] ) )
2338                                 define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
2339
2340                         if ( defined( 'HEADER_TEXTCOLOR' ) )
2341                                 $args[0]['default-text-color'] = HEADER_TEXTCOLOR;
2342                         elseif ( isset( $args[0]['default-text-color'] ) )
2343                                 define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
2344
2345                         if ( defined( 'HEADER_IMAGE' ) )
2346                                 $args[0]['default-image'] = HEADER_IMAGE;
2347                         elseif ( isset( $args[0]['default-image'] ) )
2348                                 define( 'HEADER_IMAGE', $args[0]['default-image'] );
2349
2350                         if ( $jit && ! empty( $args[0]['default-image'] ) )
2351                                 $args[0]['random-default'] = false;
2352
2353                         // If headers are supported, and we still don't have a defined width or height,
2354                         // we have implicit flex sizes.
2355                         if ( $jit ) {
2356                                 if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) )
2357                                         $args[0]['flex-width'] = true;
2358                                 if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) )
2359                                         $args[0]['flex-height'] = true;
2360                         }
2361
2362                         break;
2363
2364                 case 'custom-background' :
2365                         if ( ! is_array( $args ) )
2366                                 $args = array( 0 => array() );
2367
2368                         $defaults = array(
2369                                 'default-image'          => '',
2370                                 'default-preset'         => 'default',
2371                                 'default-position-x'     => 'left',
2372                                 'default-position-y'     => 'top',
2373                                 'default-size'           => 'auto',
2374                                 'default-repeat'         => 'repeat',
2375                                 'default-attachment'     => 'scroll',
2376                                 'default-color'          => '',
2377                                 'wp-head-callback'       => '_custom_background_cb',
2378                                 'admin-head-callback'    => '',
2379                                 'admin-preview-callback' => '',
2380                         );
2381
2382                         $jit = isset( $args[0]['__jit'] );
2383                         unset( $args[0]['__jit'] );
2384
2385                         // Merge in data from previous add_theme_support() calls. The first value registered wins.
2386                         if ( isset( $_wp_theme_features['custom-background'] ) )
2387                                 $args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
2388
2389                         if ( $jit )
2390                                 $args[0] = wp_parse_args( $args[0], $defaults );
2391
2392                         if ( defined( 'BACKGROUND_COLOR' ) )
2393                                 $args[0]['default-color'] = BACKGROUND_COLOR;
2394                         elseif ( isset( $args[0]['default-color'] ) || $jit )
2395                                 define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
2396
2397                         if ( defined( 'BACKGROUND_IMAGE' ) )
2398                                 $args[0]['default-image'] = BACKGROUND_IMAGE;
2399                         elseif ( isset( $args[0]['default-image'] ) || $jit )
2400                                 define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
2401
2402                         break;
2403
2404                 // Ensure that 'title-tag' is accessible in the admin.
2405                 case 'title-tag' :
2406                         // Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
2407                         if ( did_action( 'wp_loaded' ) ) {
2408                                 /* translators: 1: Theme support 2: hook name */
2409                                 _doing_it_wrong( "add_theme_support( 'title-tag' )", sprintf( __( 'Theme support for %1$s should be registered before the %2$s hook.' ),
2410                                         '<code>title-tag</code>', '<code>wp_loaded</code>' ), '4.1.0' );
2411
2412                                 return false;
2413                         }
2414         }
2415
2416         $_wp_theme_features[ $feature ] = $args;
2417 }
2418
2419 /**
2420  * Registers the internal custom header and background routines.
2421  *
2422  * @since 3.4.0
2423  * @access private
2424  *
2425  * @global Custom_Image_Header $custom_image_header
2426  * @global Custom_Background   $custom_background
2427  */
2428 function _custom_header_background_just_in_time() {
2429         global $custom_image_header, $custom_background;
2430
2431         if ( current_theme_supports( 'custom-header' ) ) {
2432                 // In case any constants were defined after an add_custom_image_header() call, re-run.
2433                 add_theme_support( 'custom-header', array( '__jit' => true ) );
2434
2435                 $args = get_theme_support( 'custom-header' );
2436                 if ( $args[0]['wp-head-callback'] )
2437                         add_action( 'wp_head', $args[0]['wp-head-callback'] );
2438
2439                 if ( is_admin() ) {
2440                         require_once( ABSPATH . 'wp-admin/custom-header.php' );
2441                         $custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2442                 }
2443         }
2444
2445         if ( current_theme_supports( 'custom-background' ) ) {
2446                 // In case any constants were defined after an add_custom_background() call, re-run.
2447                 add_theme_support( 'custom-background', array( '__jit' => true ) );
2448
2449                 $args = get_theme_support( 'custom-background' );
2450                 add_action( 'wp_head', $args[0]['wp-head-callback'] );
2451
2452                 if ( is_admin() ) {
2453                         require_once( ABSPATH . 'wp-admin/custom-background.php' );
2454                         $custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2455                 }
2456         }
2457 }
2458
2459 /**
2460  * Adds CSS to hide header text for custom logo, based on Customizer setting.
2461  *
2462  * @since 4.5.0
2463  * @access private
2464  */
2465 function _custom_logo_header_styles() {
2466         if ( ! current_theme_supports( 'custom-header', 'header-text' ) && get_theme_support( 'custom-logo', 'header-text' ) && ! get_theme_mod( 'header_text', true ) ) {
2467                 $classes = (array) get_theme_support( 'custom-logo', 'header-text' );
2468                 $classes = array_map( 'sanitize_html_class', $classes );
2469                 $classes = '.' . implode( ', .', $classes );
2470
2471                 ?>
2472                 <!-- Custom Logo: hide header text -->
2473                 <style id="custom-logo-css" type="text/css">
2474                         <?php echo $classes; ?> {
2475                                 position: absolute;
2476                                 clip: rect(1px, 1px, 1px, 1px);
2477                         }
2478                 </style>
2479         <?php
2480         }
2481 }
2482
2483 /**
2484  * Gets the theme support arguments passed when registering that support
2485  *
2486  * @since 3.1.0
2487  *
2488  * @global array $_wp_theme_features
2489  *
2490  * @param string $feature the feature to check
2491  * @return mixed The array of extra arguments or the value for the registered feature.
2492  */
2493 function get_theme_support( $feature ) {
2494         global $_wp_theme_features;
2495         if ( ! isset( $_wp_theme_features[ $feature ] ) )
2496                 return false;
2497
2498         if ( func_num_args() <= 1 )
2499                 return $_wp_theme_features[ $feature ];
2500
2501         $args = array_slice( func_get_args(), 1 );
2502         switch ( $feature ) {
2503                 case 'custom-logo' :
2504                 case 'custom-header' :
2505                 case 'custom-background' :
2506                         if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) )
2507                                 return $_wp_theme_features[ $feature ][0][ $args[0] ];
2508                         return false;
2509
2510                 default :
2511                         return $_wp_theme_features[ $feature ];
2512         }
2513 }
2514
2515 /**
2516  * Allows a theme to de-register its support of a certain feature
2517  *
2518  * Should be called in the theme's functions.php file. Generally would
2519  * be used for child themes to override support from the parent theme.
2520  *
2521  * @since 3.0.0
2522  * @see add_theme_support()
2523  * @param string $feature the feature being added
2524  * @return bool|void Whether feature was removed.
2525  */
2526 function remove_theme_support( $feature ) {
2527         // Blacklist: for internal registrations not used directly by themes.
2528         if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ) ) )
2529                 return false;
2530
2531         return _remove_theme_support( $feature );
2532 }
2533
2534 /**
2535  * Do not use. Removes theme support internally, ignorant of the blacklist.
2536  *
2537  * @access private
2538  * @since 3.1.0
2539  *
2540  * @global array               $_wp_theme_features
2541  * @global Custom_Image_Header $custom_image_header
2542  * @global Custom_Background   $custom_background
2543  *
2544  * @param string $feature
2545  */
2546 function _remove_theme_support( $feature ) {
2547         global $_wp_theme_features;
2548
2549         switch ( $feature ) {
2550                 case 'custom-header-uploads' :
2551                         if ( ! isset( $_wp_theme_features['custom-header'] ) )
2552                                 return false;
2553                         add_theme_support( 'custom-header', array( 'uploads' => false ) );
2554                         return; // Do not continue - custom-header-uploads no longer exists.
2555         }
2556
2557         if ( ! isset( $_wp_theme_features[ $feature ] ) )
2558                 return false;
2559
2560         switch ( $feature ) {
2561                 case 'custom-header' :
2562                         if ( ! did_action( 'wp_loaded' ) )
2563                                 break;
2564                         $support = get_theme_support( 'custom-header' );
2565                         if ( isset( $support[0]['wp-head-callback'] ) ) {
2566                                 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2567                         }
2568                         if ( isset( $GLOBALS['custom_image_header'] ) ) {
2569                                 remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
2570                                 unset( $GLOBALS['custom_image_header'] );
2571                         }
2572                         break;
2573
2574                 case 'custom-background' :
2575                         if ( ! did_action( 'wp_loaded' ) )
2576                                 break;
2577                         $support = get_theme_support( 'custom-background' );
2578                         remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2579                         remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
2580                         unset( $GLOBALS['custom_background'] );
2581                         break;
2582         }
2583
2584         unset( $_wp_theme_features[ $feature ] );
2585         return true;
2586 }
2587
2588 /**
2589  * Checks a theme's support for a given feature
2590  *
2591  * @since 2.9.0
2592  *
2593  * @global array $_wp_theme_features
2594  *
2595  * @param string $feature the feature being checked
2596  * @return bool
2597  */
2598 function current_theme_supports( $feature ) {
2599         global $_wp_theme_features;
2600
2601         if ( 'custom-header-uploads' == $feature )
2602                 return current_theme_supports( 'custom-header', 'uploads' );
2603
2604         if ( !isset( $_wp_theme_features[$feature] ) )
2605                 return false;
2606
2607         // If no args passed then no extra checks need be performed
2608         if ( func_num_args() <= 1 )
2609                 return true;
2610
2611         $args = array_slice( func_get_args(), 1 );
2612
2613         switch ( $feature ) {
2614                 case 'post-thumbnails':
2615                         // post-thumbnails can be registered for only certain content/post types by passing
2616                         // an array of types to add_theme_support(). If no array was passed, then
2617                         // any type is accepted
2618                         if ( true === $_wp_theme_features[$feature] )  // Registered for all types
2619                                 return true;
2620                         $content_type = $args[0];
2621                         return in_array( $content_type, $_wp_theme_features[$feature][0] );
2622
2623                 case 'html5':
2624                 case 'post-formats':
2625                         // specific post formats can be registered by passing an array of types to
2626                         // add_theme_support()
2627
2628                         // Specific areas of HTML5 support *must* be passed via an array to add_theme_support()
2629
2630                         $type = $args[0];
2631                         return in_array( $type, $_wp_theme_features[$feature][0] );
2632
2633                 case 'custom-logo':
2634                 case 'custom-header':
2635                 case 'custom-background':
2636                         // Specific capabilities can be registered by passing an array to add_theme_support().
2637                         return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
2638         }
2639
2640         /**
2641          * Filters whether the current theme supports a specific feature.
2642          *
2643          * The dynamic portion of the hook name, `$feature`, refers to the specific theme
2644          * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
2645          * 'custom-header', 'menus', 'automatic-feed-links', 'html5',
2646          * 'starter-content', and 'customize-selective-refresh-widgets'.
2647          *
2648          * @since 3.4.0
2649          *
2650          * @param bool   true     Whether the current theme supports the given feature. Default true.
2651          * @param array  $args    Array of arguments for the feature.
2652          * @param string $feature The theme feature.
2653          */
2654         return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[$feature] );
2655 }
2656
2657 /**
2658  * Checks a theme's support for a given feature before loading the functions which implement it.
2659  *
2660  * @since 2.9.0
2661  *
2662  * @param string $feature The feature being checked.
2663  * @param string $include Path to the file.
2664  * @return bool True if the current theme supports the supplied feature, false otherwise.
2665  */
2666 function require_if_theme_supports( $feature, $include ) {
2667         if ( current_theme_supports( $feature ) ) {
2668                 require ( $include );
2669                 return true;
2670         }
2671         return false;
2672 }
2673
2674 /**
2675  * Checks an attachment being deleted to see if it's a header or background image.
2676  *
2677  * If true it removes the theme modification which would be pointing at the deleted
2678  * attachment.
2679  *
2680  * @access private
2681  * @since 3.0.0
2682  * @since 4.3.0 Also removes `header_image_data`.
2683  * @since 4.5.0 Also removes custom logo theme mods.
2684  *
2685  * @param int $id The attachment id.
2686  */
2687 function _delete_attachment_theme_mod( $id ) {
2688         $attachment_image = wp_get_attachment_url( $id );
2689         $header_image     = get_header_image();
2690         $background_image = get_background_image();
2691         $custom_logo_id   = get_theme_mod( 'custom_logo' );
2692
2693         if ( $custom_logo_id && $custom_logo_id == $id ) {
2694                 remove_theme_mod( 'custom_logo' );
2695                 remove_theme_mod( 'header_text' );
2696         }
2697
2698         if ( $header_image && $header_image == $attachment_image ) {
2699                 remove_theme_mod( 'header_image' );
2700                 remove_theme_mod( 'header_image_data' );
2701         }
2702
2703         if ( $background_image && $background_image == $attachment_image ) {
2704                 remove_theme_mod( 'background_image' );
2705         }
2706 }
2707
2708 /**
2709  * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
2710  *
2711  * See {@see 'after_switch_theme'}.
2712  *
2713  * @since 3.3.0
2714  */
2715 function check_theme_switched() {
2716         if ( $stylesheet = get_option( 'theme_switched' ) ) {
2717                 $old_theme = wp_get_theme( $stylesheet );
2718
2719                 // Prevent retrieve_widgets() from running since Customizer already called it up front
2720                 if ( get_option( 'theme_switched_via_customizer' ) ) {
2721                         remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
2722                         update_option( 'theme_switched_via_customizer', false );
2723                 }
2724
2725                 if ( $old_theme->exists() ) {
2726                         /**
2727                          * Fires on the first WP load after a theme switch if the old theme still exists.
2728                          *
2729                          * This action fires multiple times and the parameters differs
2730                          * according to the context, if the old theme exists or not.
2731                          * If the old theme is missing, the parameter will be the slug
2732                          * of the old theme.
2733                          *
2734                          * @since 3.3.0
2735                          *
2736                          * @param string   $old_name  Old theme name.
2737                          * @param WP_Theme $old_theme WP_Theme instance of the old theme.
2738                          */
2739                         do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
2740                 } else {
2741                         /** This action is documented in wp-includes/theme.php */
2742                         do_action( 'after_switch_theme', $stylesheet );
2743                 }
2744                 flush_rewrite_rules();
2745
2746                 update_option( 'theme_switched', false );
2747         }
2748 }
2749
2750 /**
2751  * Includes and instantiates the WP_Customize_Manager class.
2752  *
2753  * Loads the Customizer at plugins_loaded when accessing the customize.php admin
2754  * page or when any request includes a wp_customize=on param or a customize_changeset
2755  * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
2756  * WordPress is loading, especially in the Customizer preview
2757  * or when making Customizer Ajax requests for widgets or menus.
2758  *
2759  * @since 3.4.0
2760  *
2761  * @global WP_Customize_Manager $wp_customize
2762  */
2763 function _wp_customize_include() {
2764
2765         $is_customize_admin_page = ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) );
2766         $should_include = (
2767                 $is_customize_admin_page
2768                 ||
2769                 ( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
2770                 ||
2771                 ( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
2772         );
2773
2774         if ( ! $should_include ) {
2775                 return;
2776         }
2777
2778         /*
2779          * Note that wp_unslash() is not being used on the input vars because it is
2780          * called before wp_magic_quotes() gets called. Besides this fact, none of
2781          * the values should contain any characters needing slashes anyway.
2782          */
2783         $keys = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel' );
2784         $input_vars = array_merge(
2785                 wp_array_slice_assoc( $_GET, $keys ),
2786                 wp_array_slice_assoc( $_POST, $keys )
2787         );
2788
2789         $theme = null;
2790         $changeset_uuid = null;
2791         $messenger_channel = null;
2792
2793         if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
2794                 $changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
2795         } elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
2796                 $changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
2797         }
2798
2799         // Note that theme will be sanitized via WP_Theme.
2800         if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
2801                 $theme = $input_vars['theme'];
2802         } elseif ( isset( $input_vars['customize_theme'] ) ) {
2803                 $theme = $input_vars['customize_theme'];
2804         }
2805         if ( isset( $input_vars['customize_messenger_channel'] ) ) {
2806                 $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
2807         }
2808
2809         require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2810         $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel' ) );
2811 }
2812
2813 /**
2814  * Publish a snapshot's changes.
2815  *
2816  * @param string  $new_status     New post status.
2817  * @param string  $old_status     Old post status.
2818  * @param WP_Post $changeset_post Changeset post object.
2819  */
2820 function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
2821         global $wp_customize, $wpdb;
2822
2823         $is_publishing_changeset = (
2824                 'customize_changeset' === $changeset_post->post_type
2825                 &&
2826                 'publish' === $new_status
2827                 &&
2828                 'publish' !== $old_status
2829         );
2830         if ( ! $is_publishing_changeset ) {
2831                 return;
2832         }
2833
2834         if ( empty( $wp_customize ) ) {
2835                 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2836                 $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $changeset_post->post_name ) );
2837         }
2838
2839         if ( ! did_action( 'customize_register' ) ) {
2840                 /*
2841                  * When running from CLI or Cron, the customize_register action will need
2842                  * to be triggered in order for core, themes, and plugins to register their
2843                  * settings. Normally core will add_action( 'customize_register' ) at
2844                  * priority 10 to register the core settings, and if any themes/plugins
2845                  * also add_action( 'customize_register' ) at the same priority, they
2846                  * will have a $wp_customize with those settings registered since they
2847                  * call add_action() afterward, normally. However, when manually doing
2848                  * the customize_register action after the setup_theme, then the order
2849                  * will be reversed for two actions added at priority 10, resulting in
2850                  * the core settings no longer being available as expected to themes/plugins.
2851                  * So the following manually calls the method that registers the core
2852                  * settings up front before doing the action.
2853                  */
2854                 remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
2855                 $wp_customize->register_controls();
2856
2857                 /** This filter is documented in /wp-includes/class-wp-customize-manager.php */
2858                 do_action( 'customize_register', $wp_customize );
2859         }
2860         $wp_customize->_publish_changeset_values( $changeset_post->ID ) ;
2861
2862         /*
2863          * Trash the changeset post if revisions are not enabled. Unpublished
2864          * changesets by default get garbage collected due to the auto-draft status.
2865          * When a changeset post is published, however, it would no longer get cleaned
2866          * out. Ths is a problem when the changeset posts are never displayed anywhere,
2867          * since they would just be endlessly piling up. So here we use the revisions
2868          * feature to indicate whether or not a published changeset should get trashed
2869          * and thus garbage collected.
2870          */
2871         if ( ! wp_revisions_enabled( $changeset_post ) ) {
2872                 $post = $changeset_post;
2873                 $post_id = $changeset_post->ID;
2874
2875                 /*
2876                  * The following re-formulates the logic from wp_trash_post() as done in
2877                  * wp_publish_post(). The reason for bypassing wp_trash_post() is that it
2878                  * will mutate the the post_content and the post_name when they should be
2879                  * untouched.
2880                  */
2881                 if ( ! EMPTY_TRASH_DAYS ) {
2882                         wp_delete_post( $post_id, true );
2883                 } else {
2884                         /** This action is documented in wp-includes/post.php */
2885                         do_action( 'wp_trash_post', $post_id );
2886
2887                         add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
2888                         add_post_meta( $post_id, '_wp_trash_meta_time', time() );
2889
2890                         $old_status = $post->post_status;
2891                         $new_status = 'trash';
2892                         $wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $post->ID ) );
2893                         clean_post_cache( $post->ID );
2894
2895                         $post->post_status = $new_status;
2896                         wp_transition_post_status( $new_status, $old_status, $post );
2897
2898                         /** This action is documented in wp-includes/post.php */
2899                         do_action( 'edit_post', $post->ID, $post );
2900
2901                         /** This action is documented in wp-includes/post.php */
2902                         do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
2903
2904                         /** This action is documented in wp-includes/post.php */
2905                         do_action( 'save_post', $post->ID, $post, true );
2906
2907                         /** This action is documented in wp-includes/post.php */
2908                         do_action( 'wp_insert_post', $post->ID, $post, true );
2909
2910                         /** This action is documented in wp-includes/post.php */
2911                         do_action( 'trashed_post', $post_id );
2912                 }
2913         }
2914 }
2915
2916 /**
2917  * Filters changeset post data upon insert to ensure post_name is intact.
2918  *
2919  * This is needed to prevent the post_name from being dropped when the post is
2920  * transitioned into pending status by a contributor.
2921  *
2922  * @since 4.7.0
2923  * @see wp_insert_post()
2924  *
2925  * @param array $post_data          An array of slashed post data.
2926  * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
2927  * @returns array Filtered data.
2928  */
2929 function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
2930         if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
2931
2932                 // Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
2933                 if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
2934                         $post_data['post_name'] = $supplied_post_data['post_name'];
2935                 }
2936         }
2937         return $post_data;
2938 }
2939
2940 /**
2941  * Adds settings for the customize-loader script.
2942  *
2943  * @since 3.4.0
2944  */
2945 function _wp_customize_loader_settings() {
2946         $admin_origin = parse_url( admin_url() );
2947         $home_origin  = parse_url( home_url() );
2948         $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
2949
2950         $browser = array(
2951                 'mobile' => wp_is_mobile(),
2952                 'ios'    => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
2953         );
2954
2955         $settings = array(
2956                 'url'           => esc_url( admin_url( 'customize.php' ) ),
2957                 'isCrossDomain' => $cross_domain,
2958                 'browser'       => $browser,
2959                 'l10n'          => array(
2960                         'saveAlert'       => __( 'The changes you made will be lost if you navigate away from this page.' ),
2961                         'mainIframeTitle' => __( 'Customizer' ),
2962                 ),
2963         );
2964
2965         $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
2966
2967         $wp_scripts = wp_scripts();
2968         $data = $wp_scripts->get_data( 'customize-loader', 'data' );
2969         if ( $data )
2970                 $script = "$data\n$script";
2971
2972         $wp_scripts->add_data( 'customize-loader', 'data', $script );
2973 }
2974
2975 /**
2976  * Returns a URL to load the Customizer.
2977  *
2978  * @since 3.4.0
2979  *
2980  * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
2981  *                               The theme's stylesheet will be urlencoded if necessary.
2982  * @return string
2983  */
2984 function wp_customize_url( $stylesheet = null ) {
2985         $url = admin_url( 'customize.php' );
2986         if ( $stylesheet )
2987                 $url .= '?theme=' . urlencode( $stylesheet );
2988         return esc_url( $url );
2989 }
2990
2991 /**
2992  * Prints a script to check whether or not the Customizer is supported,
2993  * and apply either the no-customize-support or customize-support class
2994  * to the body.
2995  *
2996  * This function MUST be called inside the body tag.
2997  *
2998  * Ideally, call this function immediately after the body tag is opened.
2999  * This prevents a flash of unstyled content.
3000  *
3001  * It is also recommended that you add the "no-customize-support" class
3002  * to the body tag by default.
3003  *
3004  * @since 3.4.0
3005  * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
3006  */
3007 function wp_customize_support_script() {
3008         $admin_origin = parse_url( admin_url() );
3009         $home_origin  = parse_url( home_url() );
3010         $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
3011
3012         ?>
3013         <!--[if lte IE 8]>
3014                 <script type="text/javascript">
3015                         document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, '' ) + ' no-customize-support';
3016                 </script>
3017         <![endif]-->
3018         <!--[if gte IE 9]><!-->
3019                 <script type="text/javascript">
3020                         (function() {
3021                                 var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
3022
3023                 <?php   if ( $cross_domain ) : ?>
3024                                 request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
3025                 <?php   else : ?>
3026                                 request = true;
3027                 <?php   endif; ?>
3028
3029                                 b[c] = b[c].replace( rcs, ' ' );
3030                                 // The customizer requires postMessage and CORS (if the site is cross domain)
3031                                 b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
3032                         }());
3033                 </script>
3034         <!--<![endif]-->
3035         <?php
3036 }
3037
3038 /**
3039  * Whether the site is being previewed in the Customizer.
3040  *
3041  * @since 4.0.0
3042  *
3043  * @global WP_Customize_Manager $wp_customize Customizer instance.
3044  *
3045  * @return bool True if the site is being previewed in the Customizer, false otherwise.
3046  */
3047 function is_customize_preview() {
3048         global $wp_customize;
3049
3050         return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();
3051 }