3 * Theme, template, and stylesheet functions.
10 * Returns an array of WP_Theme objects based on the arguments.
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.
17 * @global array $wp_theme_directories
18 * @staticvar array $_themes
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.
30 function wp_get_themes( $args = array() ) {
31 global $wp_theme_directories;
33 $defaults = array( 'errors' => false, 'allowed' => null, 'blog_id' => 0 );
34 $args = wp_parse_args( $args, $defaults );
36 $theme_directories = search_theme_directories();
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;
50 if ( empty( $theme_directories ) )
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'] ) );
60 $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
62 $theme_directories = array_diff_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
66 static $_themes = array();
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 ];
72 $themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
75 if ( null !== $args['errors'] ) {
76 foreach ( $themes as $theme => $wp_theme ) {
77 if ( $wp_theme->errors() != $args['errors'] )
78 unset( $themes[ $theme ] );
86 * Gets a WP_Theme object for a theme.
90 * @global array $wp_theme_directories
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.
97 function wp_get_theme( $stylesheet = null, $theme_root = null ) {
98 global $wp_theme_directories;
100 if ( empty( $stylesheet ) )
101 $stylesheet = get_stylesheet();
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;
111 return new WP_Theme( $stylesheet, $theme_root );
115 * Clears the cache held by get_theme_roots() and WP_Theme.
118 * @param bool $clear_update_cache Whether to clear the Theme updates cache
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();
129 * Whether a child theme is in use.
133 * @return bool true if a child theme is in use, false otherwise.
135 function is_child_theme() {
136 return ( TEMPLATEPATH !== STYLESHEETPATH );
140 * Retrieve name of the current stylesheet.
142 * The theme name that the administrator has currently set the front end theme
145 * For all intents and purposes, the template name and the stylesheet name are
146 * going to be the same for most cases.
150 * @return string Stylesheet name.
152 function get_stylesheet() {
154 * Filters the name of current stylesheet.
158 * @param string $stylesheet Name of the current stylesheet.
160 return apply_filters( 'stylesheet', get_option( 'stylesheet' ) );
164 * Retrieve stylesheet directory path for current theme.
168 * @return string Path to current theme directory.
170 function get_stylesheet_directory() {
171 $stylesheet = get_stylesheet();
172 $theme_root = get_theme_root( $stylesheet );
173 $stylesheet_dir = "$theme_root/$stylesheet";
176 * Filters the stylesheet directory path for current theme.
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.
184 return apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
188 * Retrieve stylesheet directory URI.
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";
200 * Filters the stylesheet directory URI.
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.
208 return apply_filters( 'stylesheet_directory_uri', $stylesheet_dir_uri, $stylesheet, $theme_root_uri );
212 * Retrieves the URI of current theme stylesheet.
214 * The stylesheet file name is 'style.css' which is appended to the stylesheet directory URI path.
215 * See get_stylesheet_directory_uri().
221 function get_stylesheet_uri() {
222 $stylesheet_dir_uri = get_stylesheet_directory_uri();
223 $stylesheet_uri = $stylesheet_dir_uri . '/style.css';
225 * Filters the URI of the current theme stylesheet.
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.
232 return apply_filters( 'stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
236 * Retrieves the localized stylesheet URI.
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'.
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.
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.
252 * @global WP_Locale $wp_locale
256 function get_locale_stylesheet_uri() {
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";
266 $stylesheet_uri = '';
268 * Filters the localized stylesheet URI.
272 * @param string $stylesheet_uri Localized stylesheet URI.
273 * @param string $stylesheet_dir_uri Stylesheet directory URI.
275 return apply_filters( 'locale_stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri );
279 * Retrieve name of the current theme.
283 * @return string Template name.
285 function get_template() {
287 * Filters the name of the current theme.
291 * @param string $template Current theme's directory name.
293 return apply_filters( 'template', get_option( 'template' ) );
297 * Retrieve current theme directory.
301 * @return string Template directory path.
303 function get_template_directory() {
304 $template = get_template();
305 $theme_root = get_theme_root( $template );
306 $template_dir = "$theme_root/$template";
309 * Filters the current theme directory path.
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.
317 return apply_filters( 'template_directory', $template_dir, $template, $theme_root );
321 * Retrieve theme directory URI.
325 * @return string Template directory URI.
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";
333 * Filters the current theme directory URI.
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.
341 return apply_filters( 'template_directory_uri', $template_dir_uri, $template, $theme_root_uri );
345 * Retrieve theme roots.
349 * @global array $wp_theme_directories
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.
353 function get_theme_roots() {
354 global $wp_theme_directories;
356 if ( count($wp_theme_directories) <= 1 )
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' );
368 * Register a directory that contains themes.
372 * @global array $wp_theme_directories
374 * @param string $directory Either the full filesystem path to a theme folder or a folder within WP_CONTENT_DIR
377 function register_theme_directory( $directory ) {
378 global $wp_theme_directories;
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 ) ) {
389 if ( ! is_array( $wp_theme_directories ) ) {
390 $wp_theme_directories = array();
393 $untrailed = untrailingslashit( $directory );
394 if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories ) ) {
395 $wp_theme_directories[] = $untrailed;
402 * Search all registered theme directories for complete and valid themes.
406 * @global array $wp_theme_directories
407 * @staticvar array $found_themes
409 * @param bool $force Optional. Whether to force a new directory scan. Defaults to false.
410 * @return array|false Valid themes found
412 function search_theme_directories( $force = false ) {
413 global $wp_theme_directories;
414 static $found_themes = null;
416 if ( empty( $wp_theme_directories ) )
419 if ( ! $force && isset( $found_themes ) )
420 return $found_themes;
422 $found_themes = array();
424 $wp_theme_directories = (array) $wp_theme_directories;
425 $relative_theme_roots = array();
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;
434 $relative_theme_roots[ $theme_root ] = $theme_root;
438 * Filters whether to get the cache of the registered theme directories.
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.
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 ] ) )
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.
457 return $found_themes;
459 if ( ! is_int( $cache_expiration ) )
460 $cache_expiration = 1800; // half hour
462 $cache_expiration = 1800; // half hour
465 /* Loop the registered theme directories and extract all themes */
466 foreach ( $wp_theme_directories as $theme_root ) {
468 // Start with directories in the root of the current theme directory.
469 $dirs = @ scandir( $theme_root );
471 trigger_error( "$theme_root is not readable", E_USER_NOTICE );
474 foreach ( $dirs as $dir ) {
475 if ( ! is_dir( $theme_root . '/' . $dir ) || $dir[0] == '.' || $dir == 'CVS' )
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,
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 );
490 trigger_error( "$theme_root/$dir is not readable", E_USER_NOTICE );
493 foreach ( $sub_dirs as $sub_dir ) {
494 if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || $dir[0] == '.' || $dir == 'CVS' )
496 if ( ! file_exists( $theme_root . '/' . $dir . '/' . $sub_dir . '/style.css' ) )
498 $found_themes[ $dir . '/' . $sub_dir ] = array(
499 'theme_file' => $dir . '/' . $sub_dir . '/style.css',
500 'theme_root' => $theme_root,
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,
515 asort( $found_themes );
517 $theme_roots = array();
518 $relative_theme_roots = array_flip( $relative_theme_roots );
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.
524 if ( $theme_roots != get_site_transient( 'theme_roots' ) )
525 set_site_transient( 'theme_roots', $theme_roots, $cache_expiration );
527 return $found_themes;
531 * Retrieve path to themes directory.
533 * Does not have trailing slash.
537 * @global array $wp_theme_directories
539 * @param string $stylesheet_or_template The stylesheet or template name of the theme
540 * @return string Theme path.
542 function get_theme_root( $stylesheet_or_template = false ) {
543 global $wp_theme_directories;
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;
551 $theme_root = WP_CONTENT_DIR . '/themes';
555 * Filters the absolute path to the themes directory.
559 * @param string $theme_root Absolute path to themes directory.
561 return apply_filters( 'theme_root', $theme_root );
565 * Retrieve URI for themes directory.
567 * Does not have trailing slash.
571 * @global array $wp_theme_directories
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.
579 function get_theme_root_uri( $stylesheet_or_template = false, $theme_root = false ) {
580 global $wp_theme_directories;
582 if ( $stylesheet_or_template && ! $theme_root )
583 $theme_root = get_raw_theme_root( $stylesheet_or_template );
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 );
595 $theme_root_uri = $theme_root;
597 $theme_root_uri = content_url( $theme_root );
600 $theme_root_uri = content_url( 'themes' );
604 * Filters the URI for themes directory.
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.
612 return apply_filters( 'theme_root_uri', $theme_root_uri, get_option( 'siteurl' ), $stylesheet_or_template );
616 * Get the raw theme root relative to the content directory with no filters applied.
620 * @global array $wp_theme_directories
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
627 function get_raw_theme_root( $stylesheet_or_template, $skip_cache = false ) {
628 global $wp_theme_directories;
630 if ( count($wp_theme_directories) <= 1 )
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');
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];
653 * Display localized stylesheet link element.
657 function locale_stylesheet() {
658 $stylesheet = get_locale_stylesheet_uri();
659 if ( empty($stylesheet) )
661 echo '<link rel="stylesheet" href="' . $stylesheet . '" type="text/css" media="screen" />';
665 * Switches the theme.
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.
672 * @global array $wp_theme_directories
673 * @global WP_Customize_Manager $wp_customize
674 * @global array $sidebars_widgets
676 * @param string $stylesheet Stylesheet name
678 function switch_theme( $stylesheet ) {
679 global $wp_theme_directories, $wp_customize, $sidebars_widgets;
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;
688 if ( is_array( $_sidebars_widgets ) ) {
689 set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $_sidebars_widgets ) );
692 $nav_menu_locations = get_theme_mod( 'nav_menu_locations' );
694 if ( func_num_args() > 1 ) {
695 $stylesheet = func_get_arg( 1 );
698 $old_theme = wp_get_theme();
699 $new_theme = wp_get_theme( $stylesheet );
700 $template = $new_theme->get_template();
702 update_option( 'template', $template );
703 update_option( 'stylesheet', $stylesheet );
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 ) );
709 delete_option( 'template_root' );
710 delete_option( 'stylesheet_root' );
713 $new_name = $new_theme->get('Name');
715 update_option( 'current_theme', $new_name );
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;
723 add_option( "theme_mods_$stylesheet", $default_theme_mods );
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.
730 if ( 'wp_ajax_customize_save' === current_action() ) {
731 remove_theme_mod( 'sidebars_widgets' );
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 );
742 update_option( 'theme_switched', $old_theme->get_stylesheet() );
745 * Fires after the theme is switched.
748 * @since 4.5.0 Introduced the `$old_theme` parameter.
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.
754 do_action( 'switch_theme', $new_name, $new_theme, $old_theme );
758 * Checks that current theme files 'index.php' and 'style.css' exists.
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.
764 * You can use the {@see 'validate_current_theme'} filter to return false to
765 * disable this functionality.
768 * @see WP_DEFAULT_THEME
772 function validate_current_theme() {
774 * Filters whether to validate the current theme.
778 * @param bool $validate Whether to validate the current theme. Default true.
780 if ( wp_installing() || ! apply_filters( 'validate_current_theme', true ) )
783 if ( ! file_exists( get_template_directory() . '/index.php' ) ) {
785 } elseif ( ! file_exists( get_template_directory() . '/style.css' ) ) {
787 } elseif ( is_child_theme() && ! file_exists( get_stylesheet_directory() . '/style.css' ) ) {
794 $default = wp_get_theme( WP_DEFAULT_THEME );
795 if ( $default->exists() ) {
796 switch_theme( WP_DEFAULT_THEME );
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`.)
809 $default = WP_Theme::get_core_default_theme();
810 if ( false === $default || get_stylesheet() == $default->get_stylesheet() ) {
814 switch_theme( $default->get_stylesheet() );
819 * Retrieve all theme modifications.
823 * @return array|void Theme modifications.
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" );
842 * Retrieve theme modification value for the current theme.
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
851 * @param string $name Theme modification name.
852 * @param bool|string $default
855 function get_theme_mod( $name, $default = false ) {
856 $mods = get_theme_mods();
858 if ( isset( $mods[$name] ) ) {
860 * Filters the theme modification, or 'theme_mod', value.
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.
869 * @param string $current_mod The value of the current theme modification.
871 return apply_filters( "theme_mod_{$name}", $mods[$name] );
874 if ( is_string( $default ) )
875 $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
877 /** This filter is documented in wp-includes/theme.php */
878 return apply_filters( "theme_mod_{$name}", $default );
882 * Update theme modification value for the current theme.
886 * @param string $name Theme modification name.
887 * @param mixed $value Theme modification value.
889 function set_theme_mod( $name, $value ) {
890 $mods = get_theme_mods();
891 $old_value = isset( $mods[ $name ] ) ? $mods[ $name ] : false;
894 * Filters the theme mod value on save.
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.
902 * @param string $value The new value of the theme mod.
903 * @param string $old_value The current value of the theme mod.
905 $mods[ $name ] = apply_filters( "pre_set_theme_mod_$name", $value, $old_value );
907 $theme = get_option( 'stylesheet' );
908 update_option( "theme_mods_$theme", $mods );
912 * Remove theme modification name from current theme list.
914 * If removing the name also removes all elements, then the entire option will
919 * @param string $name Theme modification name.
921 function remove_theme_mod( $name ) {
922 $mods = get_theme_mods();
924 if ( ! isset( $mods[ $name ] ) )
927 unset( $mods[ $name ] );
929 if ( empty( $mods ) ) {
933 $theme = get_option( 'stylesheet' );
934 update_option( "theme_mods_$theme", $mods );
938 * Remove theme modifications option for current theme.
942 function remove_theme_mods() {
943 delete_option( 'theme_mods_' . get_option( 'stylesheet' ) );
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 );
953 * Retrieves the custom header text color in HEX format.
957 * @return string Header text color in HEX format (minus the hash symbol).
959 function get_header_textcolor() {
960 return get_theme_mod('header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
964 * Displays the custom header text color in HEX format (minus the hash symbol).
968 function header_textcolor() {
969 echo get_header_textcolor();
973 * Whether to display the header text.
979 function display_header_text() {
980 if ( ! current_theme_supports( 'custom-header', 'header-text' ) )
983 $text_color = get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
984 return 'blank' !== $text_color;
988 * Check whether a header image is set or not.
992 * @see get_header_image()
994 * @return bool Whether a header image is set or not.
996 function has_header_image() {
997 return (bool) get_header_image();
1001 * Retrieve header image for custom header.
1005 * @return string|false
1007 function get_header_image() {
1008 $url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1010 if ( 'remove-header' == $url )
1013 if ( is_random_header_image() )
1014 $url = get_random_header_image();
1016 return esc_url_raw( set_url_scheme( $url ) );
1020 * Create image tag markup for a custom header image.
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.
1028 function get_header_image_tag( $attr = array() ) {
1029 $header = get_custom_header();
1031 if ( empty( $header->url ) ) {
1035 $width = absint( $header->width );
1036 $height = absint( $header->height );
1038 $attr = wp_parse_args(
1041 'src' => $header->url,
1043 'height' => $height,
1044 'alt' => get_bloginfo( 'name' ),
1048 // Generate 'srcset' and 'sizes' if not already present.
1049 if ( empty( $attr['srcset'] ) && ! empty( $header->attachment_id ) ) {
1050 $image_meta = get_post_meta( $header->attachment_id, '_wp_attachment_metadata', true );
1051 $size_array = array( $width, $height );
1053 if ( is_array( $image_meta ) ) {
1054 $srcset = wp_calculate_image_srcset( $size_array, $header->url, $image_meta, $header->attachment_id );
1055 $sizes = ! empty( $attr['sizes'] ) ? $attr['sizes'] : wp_calculate_image_sizes( $size_array, $header->url, $image_meta, $header->attachment_id );
1057 if ( $srcset && $sizes ) {
1058 $attr['srcset'] = $srcset;
1059 $attr['sizes'] = $sizes;
1064 $attr = array_map( 'esc_attr', $attr );
1067 foreach ( $attr as $name => $value ) {
1068 $html .= ' ' . $name . '="' . $value . '"';
1074 * Filters the markup of header images.
1078 * @param string $html The HTML image tag markup being filtered.
1079 * @param object $header The custom header object returned by 'get_custom_header()'.
1080 * @param array $attr Array of the attributes for the image tag.
1082 return apply_filters( 'get_header_image_tag', $html, $header, $attr );
1086 * Display the image markup for a custom header image.
1090 * @param array $attr Optional. Attributes for the image markup. Default empty.
1092 function the_header_image_tag( $attr = array() ) {
1093 echo get_header_image_tag( $attr );
1097 * Get random header image data from registered images in theme.
1103 * @global array $_wp_default_headers
1104 * @staticvar object $_wp_random_header
1108 function _get_random_header_data() {
1109 static $_wp_random_header = null;
1111 if ( empty( $_wp_random_header ) ) {
1112 global $_wp_default_headers;
1113 $header_image_mod = get_theme_mod( 'header_image', '' );
1116 if ( 'random-uploaded-image' == $header_image_mod )
1117 $headers = get_uploaded_header_images();
1118 elseif ( ! empty( $_wp_default_headers ) ) {
1119 if ( 'random-default-image' == $header_image_mod ) {
1120 $headers = $_wp_default_headers;
1122 if ( current_theme_supports( 'custom-header', 'random-default' ) )
1123 $headers = $_wp_default_headers;
1127 if ( empty( $headers ) )
1128 return new stdClass;
1130 $_wp_random_header = (object) $headers[ array_rand( $headers ) ];
1132 $_wp_random_header->url = sprintf( $_wp_random_header->url, get_template_directory_uri(), get_stylesheet_directory_uri() );
1133 $_wp_random_header->thumbnail_url = sprintf( $_wp_random_header->thumbnail_url, get_template_directory_uri(), get_stylesheet_directory_uri() );
1135 return $_wp_random_header;
1139 * Get random header image url from registered images in theme.
1143 * @return string Path to header image
1145 function get_random_header_image() {
1146 $random_image = _get_random_header_data();
1147 if ( empty( $random_image->url ) )
1149 return $random_image->url;
1153 * Check if random header image is in use.
1155 * Always true if user expressly chooses the option in Appearance > Header.
1156 * Also true if theme has multiple header images registered, no specific header image
1157 * is chosen, and theme turns on random headers with add_theme_support().
1161 * @param string $type The random pool to use. any|default|uploaded
1164 function is_random_header_image( $type = 'any' ) {
1165 $header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
1167 if ( 'any' == $type ) {
1168 if ( 'random-default-image' == $header_image_mod || 'random-uploaded-image' == $header_image_mod || ( '' != get_random_header_image() && empty( $header_image_mod ) ) )
1171 if ( "random-$type-image" == $header_image_mod )
1173 elseif ( 'default' == $type && empty( $header_image_mod ) && '' != get_random_header_image() )
1181 * Display header image URL.
1185 function header_image() {
1186 $image = get_header_image();
1188 echo esc_url( $image );
1193 * Get the header images uploaded for the current theme.
1199 function get_uploaded_header_images() {
1200 $header_images = array();
1203 $headers = get_posts( array( 'post_type' => 'attachment', 'meta_key' => '_wp_attachment_is_custom_header', 'meta_value' => get_option('stylesheet'), 'orderby' => 'none', 'nopaging' => true ) );
1205 if ( empty( $headers ) )
1208 foreach ( (array) $headers as $header ) {
1209 $url = esc_url_raw( wp_get_attachment_url( $header->ID ) );
1210 $header_data = wp_get_attachment_metadata( $header->ID );
1211 $header_index = $header->ID;
1213 $header_images[$header_index] = array();
1214 $header_images[$header_index]['attachment_id'] = $header->ID;
1215 $header_images[$header_index]['url'] = $url;
1216 $header_images[$header_index]['thumbnail_url'] = $url;
1217 $header_images[$header_index]['alt_text'] = get_post_meta( $header->ID, '_wp_attachment_image_alt', true );
1219 if ( isset( $header_data['width'] ) )
1220 $header_images[$header_index]['width'] = $header_data['width'];
1221 if ( isset( $header_data['height'] ) )
1222 $header_images[$header_index]['height'] = $header_data['height'];
1225 return $header_images;
1229 * Get the header image data.
1233 * @global array $_wp_default_headers
1237 function get_custom_header() {
1238 global $_wp_default_headers;
1240 if ( is_random_header_image() ) {
1241 $data = _get_random_header_data();
1243 $data = get_theme_mod( 'header_image_data' );
1244 if ( ! $data && current_theme_supports( 'custom-header', 'default-image' ) ) {
1245 $directory_args = array( get_template_directory_uri(), get_stylesheet_directory_uri() );
1247 $data['url'] = $data['thumbnail_url'] = vsprintf( get_theme_support( 'custom-header', 'default-image' ), $directory_args );
1248 if ( ! empty( $_wp_default_headers ) ) {
1249 foreach ( (array) $_wp_default_headers as $default_header ) {
1250 $url = vsprintf( $default_header['url'], $directory_args );
1251 if ( $data['url'] == $url ) {
1252 $data = $default_header;
1253 $data['url'] = $url;
1254 $data['thumbnail_url'] = vsprintf( $data['thumbnail_url'], $directory_args );
1264 'thumbnail_url' => '',
1265 'width' => get_theme_support( 'custom-header', 'width' ),
1266 'height' => get_theme_support( 'custom-header', 'height' ),
1268 return (object) wp_parse_args( $data, $default );
1272 * Register a selection of default headers to be displayed by the custom header admin UI.
1276 * @global array $_wp_default_headers
1278 * @param array $headers Array of headers keyed by a string id. The ids point to arrays containing 'url', 'thumbnail_url', and 'description' keys.
1280 function register_default_headers( $headers ) {
1281 global $_wp_default_headers;
1283 $_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
1287 * Unregister default headers.
1289 * This function must be called after register_default_headers() has already added the
1290 * header you want to remove.
1292 * @see register_default_headers()
1295 * @global array $_wp_default_headers
1297 * @param string|array $header The header string id (key of array) to remove, or an array thereof.
1298 * @return bool|void A single header returns true on success, false on failure.
1299 * There is currently no return value for multiple headers.
1301 function unregister_default_headers( $header ) {
1302 global $_wp_default_headers;
1303 if ( is_array( $header ) ) {
1304 array_map( 'unregister_default_headers', $header );
1305 } elseif ( isset( $_wp_default_headers[ $header ] ) ) {
1306 unset( $_wp_default_headers[ $header ] );
1314 * Retrieve background image for custom background.
1320 function get_background_image() {
1321 return get_theme_mod('background_image', get_theme_support( 'custom-background', 'default-image' ) );
1325 * Display background image path.
1329 function background_image() {
1330 echo get_background_image();
1334 * Retrieve value for custom background color.
1340 function get_background_color() {
1341 return get_theme_mod('background_color', get_theme_support( 'custom-background', 'default-color' ) );
1345 * Display background color value.
1349 function background_color() {
1350 echo get_background_color();
1354 * Default custom background callback.
1359 function _custom_background_cb() {
1360 // $background is the saved custom image, or the default image.
1361 $background = set_url_scheme( get_background_image() );
1363 // $color is the saved custom color.
1364 // A default has to be specified in style.css. It will not be printed here.
1365 $color = get_background_color();
1367 if ( $color === get_theme_support( 'custom-background', 'default-color' ) ) {
1371 if ( ! $background && ! $color )
1374 $style = $color ? "background-color: #$color;" : '';
1376 if ( $background ) {
1377 $image = " background-image: url('$background');";
1379 $repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) );
1380 if ( ! in_array( $repeat, array( 'no-repeat', 'repeat-x', 'repeat-y', 'repeat' ) ) )
1382 $repeat = " background-repeat: $repeat;";
1384 $position = get_theme_mod( 'background_position_x', get_theme_support( 'custom-background', 'default-position-x' ) );
1385 if ( ! in_array( $position, array( 'center', 'right', 'left' ) ) )
1387 $position = " background-position: top $position;";
1389 $attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) );
1390 if ( ! in_array( $attachment, array( 'fixed', 'scroll' ) ) )
1391 $attachment = 'scroll';
1392 $attachment = " background-attachment: $attachment;";
1394 $style .= $image . $repeat . $position . $attachment;
1397 <style type="text/css" id="custom-background-css">
1398 body.custom-background { <?php echo trim( $style ); ?> }
1404 * Add callback for custom TinyMCE editor stylesheets.
1406 * The parameter $stylesheet is the name of the stylesheet, relative to
1407 * the theme root. It also accepts an array of stylesheets.
1408 * It is optional and defaults to 'editor-style.css'.
1410 * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
1411 * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
1412 * If an array of stylesheets is passed to add_editor_style(),
1413 * RTL is only added for the first stylesheet.
1415 * Since version 3.4 the TinyMCE body has .rtl CSS class.
1416 * It is a better option to use that class and add any RTL styles to the main stylesheet.
1420 * @global array $editor_styles
1422 * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
1423 * Defaults to 'editor-style.css'
1425 function add_editor_style( $stylesheet = 'editor-style.css' ) {
1426 add_theme_support( 'editor-style' );
1431 global $editor_styles;
1432 $editor_styles = (array) $editor_styles;
1433 $stylesheet = (array) $stylesheet;
1435 $rtl_stylesheet = str_replace('.css', '-rtl.css', $stylesheet[0]);
1436 $stylesheet[] = $rtl_stylesheet;
1439 $editor_styles = array_merge( $editor_styles, $stylesheet );
1443 * Removes all visual editor stylesheets.
1447 * @global array $editor_styles
1449 * @return bool True on success, false if there were no stylesheets to remove.
1451 function remove_editor_styles() {
1452 if ( ! current_theme_supports( 'editor-style' ) )
1454 _remove_theme_support( 'editor-style' );
1456 $GLOBALS['editor_styles'] = array();
1461 * Retrieve any registered editor stylesheets
1465 * @global array $editor_styles Registered editor stylesheets
1467 * @return array If registered, a list of editor stylesheet URLs.
1469 function get_editor_stylesheets() {
1470 $stylesheets = array();
1471 // load editor_style.css if the current theme supports it
1472 if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
1473 $editor_styles = $GLOBALS['editor_styles'];
1475 $editor_styles = array_unique( array_filter( $editor_styles ) );
1476 $style_uri = get_stylesheet_directory_uri();
1477 $style_dir = get_stylesheet_directory();
1479 // Support externally referenced styles (like, say, fonts).
1480 foreach ( $editor_styles as $key => $file ) {
1481 if ( preg_match( '~^(https?:)?//~', $file ) ) {
1482 $stylesheets[] = esc_url_raw( $file );
1483 unset( $editor_styles[ $key ] );
1487 // Look in a parent theme first, that way child theme CSS overrides.
1488 if ( is_child_theme() ) {
1489 $template_uri = get_template_directory_uri();
1490 $template_dir = get_template_directory();
1492 foreach ( $editor_styles as $key => $file ) {
1493 if ( $file && file_exists( "$template_dir/$file" ) ) {
1494 $stylesheets[] = "$template_uri/$file";
1499 foreach ( $editor_styles as $file ) {
1500 if ( $file && file_exists( "$style_dir/$file" ) ) {
1501 $stylesheets[] = "$style_uri/$file";
1507 * Filters the array of stylesheets applied to the editor.
1511 * @param array $stylesheets Array of stylesheets to be applied to the editor.
1513 return apply_filters( 'editor_stylesheets', $stylesheets );
1517 * Registers theme support for a given feature.
1519 * Must be called in the theme's functions.php file to work.
1520 * If attached to a hook, it must be {@see 'after_setup_theme'}.
1521 * The {@see 'init'} hook may be too late for some features.
1524 * @since 3.6.0 The `html5` feature was added
1525 * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'
1526 * @since 4.1.0 The `title-tag` feature was added
1527 * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added
1529 * @global array $_wp_theme_features
1531 * @param string $feature The feature being added. Likely core values include 'post-formats',
1532 * 'post-thumbnails', 'html5', 'custom-logo', 'custom-header-uploads',
1533 * 'custom-header', 'custom-background', 'title-tag', etc.
1534 * @param mixed $args,... Optional extra arguments to pass along with certain features.
1535 * @return void|bool False on failure, void otherwise.
1537 function add_theme_support( $feature ) {
1538 global $_wp_theme_features;
1540 if ( func_num_args() == 1 )
1543 $args = array_slice( func_get_args(), 1 );
1545 switch ( $feature ) {
1546 case 'post-thumbnails':
1547 // All post types are already supported.
1548 if ( true === get_theme_support( 'post-thumbnails' ) ) {
1553 * Merge post types with any that already declared their support
1554 * for post thumbnails.
1556 if ( is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
1557 $args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
1562 case 'post-formats' :
1563 if ( is_array( $args[0] ) ) {
1564 $post_formats = get_post_format_slugs();
1565 unset( $post_formats['standard'] );
1567 $args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
1572 // You can't just pass 'html5', you need to pass an array of types.
1573 if ( empty( $args[0] ) ) {
1574 // Build an array of types for back-compat.
1575 $args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
1576 } elseif ( ! is_array( $args[0] ) ) {
1577 _doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
1581 // Calling 'html5' again merges, rather than overwrites.
1582 if ( isset( $_wp_theme_features['html5'] ) )
1583 $args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
1587 if ( ! is_array( $args ) ) {
1588 $args = array( 0 => array() );
1593 'flex-width' => false,
1594 'flex-height' => false,
1595 'header-text' => '',
1597 $args[0] = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
1599 // Allow full flexibility if no size is specified.
1600 if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
1601 $args[0]['flex-width'] = true;
1602 $args[0]['flex-height'] = true;
1606 case 'custom-header-uploads' :
1607 return add_theme_support( 'custom-header', array( 'uploads' => true ) );
1609 case 'custom-header' :
1610 if ( ! is_array( $args ) )
1611 $args = array( 0 => array() );
1614 'default-image' => '',
1615 'random-default' => false,
1618 'flex-height' => false,
1619 'flex-width' => false,
1620 'default-text-color' => '',
1621 'header-text' => true,
1623 'wp-head-callback' => '',
1624 'admin-head-callback' => '',
1625 'admin-preview-callback' => '',
1628 $jit = isset( $args[0]['__jit'] );
1629 unset( $args[0]['__jit'] );
1631 // Merge in data from previous add_theme_support() calls.
1632 // The first value registered wins. (A child theme is set up first.)
1633 if ( isset( $_wp_theme_features['custom-header'] ) )
1634 $args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
1636 // Load in the defaults at the end, as we need to insure first one wins.
1637 // This will cause all constants to be defined, as each arg will then be set to the default.
1639 $args[0] = wp_parse_args( $args[0], $defaults );
1641 // If a constant was defined, use that value. Otherwise, define the constant to ensure
1642 // the constant is always accurate (and is not defined later, overriding our value).
1643 // As stated above, the first value wins.
1644 // Once we get to wp_loaded (just-in-time), define any constants we haven't already.
1645 // Constants are lame. Don't reference them. This is just for backward compatibility.
1647 if ( defined( 'NO_HEADER_TEXT' ) )
1648 $args[0]['header-text'] = ! NO_HEADER_TEXT;
1649 elseif ( isset( $args[0]['header-text'] ) )
1650 define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
1652 if ( defined( 'HEADER_IMAGE_WIDTH' ) )
1653 $args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
1654 elseif ( isset( $args[0]['width'] ) )
1655 define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
1657 if ( defined( 'HEADER_IMAGE_HEIGHT' ) )
1658 $args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
1659 elseif ( isset( $args[0]['height'] ) )
1660 define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
1662 if ( defined( 'HEADER_TEXTCOLOR' ) )
1663 $args[0]['default-text-color'] = HEADER_TEXTCOLOR;
1664 elseif ( isset( $args[0]['default-text-color'] ) )
1665 define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
1667 if ( defined( 'HEADER_IMAGE' ) )
1668 $args[0]['default-image'] = HEADER_IMAGE;
1669 elseif ( isset( $args[0]['default-image'] ) )
1670 define( 'HEADER_IMAGE', $args[0]['default-image'] );
1672 if ( $jit && ! empty( $args[0]['default-image'] ) )
1673 $args[0]['random-default'] = false;
1675 // If headers are supported, and we still don't have a defined width or height,
1676 // we have implicit flex sizes.
1678 if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) )
1679 $args[0]['flex-width'] = true;
1680 if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) )
1681 $args[0]['flex-height'] = true;
1686 case 'custom-background' :
1687 if ( ! is_array( $args ) )
1688 $args = array( 0 => array() );
1691 'default-image' => '',
1692 'default-repeat' => 'repeat',
1693 'default-position-x' => 'left',
1694 'default-attachment' => 'scroll',
1695 'default-color' => '',
1696 'wp-head-callback' => '_custom_background_cb',
1697 'admin-head-callback' => '',
1698 'admin-preview-callback' => '',
1701 $jit = isset( $args[0]['__jit'] );
1702 unset( $args[0]['__jit'] );
1704 // Merge in data from previous add_theme_support() calls. The first value registered wins.
1705 if ( isset( $_wp_theme_features['custom-background'] ) )
1706 $args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
1709 $args[0] = wp_parse_args( $args[0], $defaults );
1711 if ( defined( 'BACKGROUND_COLOR' ) )
1712 $args[0]['default-color'] = BACKGROUND_COLOR;
1713 elseif ( isset( $args[0]['default-color'] ) || $jit )
1714 define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
1716 if ( defined( 'BACKGROUND_IMAGE' ) )
1717 $args[0]['default-image'] = BACKGROUND_IMAGE;
1718 elseif ( isset( $args[0]['default-image'] ) || $jit )
1719 define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
1723 // Ensure that 'title-tag' is accessible in the admin.
1725 // Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
1726 if ( did_action( 'wp_loaded' ) ) {
1727 /* translators: 1: Theme support 2: hook name */
1728 _doing_it_wrong( "add_theme_support( 'title-tag' )", sprintf( __( 'Theme support for %1$s should be registered before the %2$s hook.' ),
1729 '<code>title-tag</code>', '<code>wp_loaded</code>' ), '4.1.0' );
1735 $_wp_theme_features[ $feature ] = $args;
1739 * Registers the internal custom header and background routines.
1744 * @global Custom_Image_Header $custom_image_header
1745 * @global Custom_Background $custom_background
1747 function _custom_header_background_just_in_time() {
1748 global $custom_image_header, $custom_background;
1750 if ( current_theme_supports( 'custom-header' ) ) {
1751 // In case any constants were defined after an add_custom_image_header() call, re-run.
1752 add_theme_support( 'custom-header', array( '__jit' => true ) );
1754 $args = get_theme_support( 'custom-header' );
1755 if ( $args[0]['wp-head-callback'] )
1756 add_action( 'wp_head', $args[0]['wp-head-callback'] );
1759 require_once( ABSPATH . 'wp-admin/custom-header.php' );
1760 $custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
1764 if ( current_theme_supports( 'custom-background' ) ) {
1765 // In case any constants were defined after an add_custom_background() call, re-run.
1766 add_theme_support( 'custom-background', array( '__jit' => true ) );
1768 $args = get_theme_support( 'custom-background' );
1769 add_action( 'wp_head', $args[0]['wp-head-callback'] );
1772 require_once( ABSPATH . 'wp-admin/custom-background.php' );
1773 $custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
1779 * Adds CSS to hide header text for custom logo, based on Customizer setting.
1784 function _custom_logo_header_styles() {
1785 if ( ! current_theme_supports( 'custom-header', 'header-text' ) && get_theme_support( 'custom-logo', 'header-text' ) && ! get_theme_mod( 'header_text', true ) ) {
1786 $classes = (array) get_theme_support( 'custom-logo', 'header-text' );
1787 $classes = array_map( 'sanitize_html_class', $classes );
1788 $classes = '.' . implode( ', .', $classes );
1791 <!-- Custom Logo: hide header text -->
1792 <style id="custom-logo-css" type="text/css">
1793 <?php echo $classes; ?> {
1795 clip: rect(1px, 1px, 1px, 1px);
1803 * Gets the theme support arguments passed when registering that support
1807 * @global array $_wp_theme_features
1809 * @param string $feature the feature to check
1810 * @return mixed The array of extra arguments or the value for the registered feature.
1812 function get_theme_support( $feature ) {
1813 global $_wp_theme_features;
1814 if ( ! isset( $_wp_theme_features[ $feature ] ) )
1817 if ( func_num_args() <= 1 )
1818 return $_wp_theme_features[ $feature ];
1820 $args = array_slice( func_get_args(), 1 );
1821 switch ( $feature ) {
1822 case 'custom-logo' :
1823 case 'custom-header' :
1824 case 'custom-background' :
1825 if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) )
1826 return $_wp_theme_features[ $feature ][0][ $args[0] ];
1830 return $_wp_theme_features[ $feature ];
1835 * Allows a theme to de-register its support of a certain feature
1837 * Should be called in the theme's functions.php file. Generally would
1838 * be used for child themes to override support from the parent theme.
1841 * @see add_theme_support()
1842 * @param string $feature the feature being added
1843 * @return bool|void Whether feature was removed.
1845 function remove_theme_support( $feature ) {
1846 // Blacklist: for internal registrations not used directly by themes.
1847 if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ) ) )
1850 return _remove_theme_support( $feature );
1854 * Do not use. Removes theme support internally, ignorant of the blacklist.
1859 * @global array $_wp_theme_features
1860 * @global Custom_Image_Header $custom_image_header
1861 * @global Custom_Background $custom_background
1863 * @param string $feature
1865 function _remove_theme_support( $feature ) {
1866 global $_wp_theme_features;
1868 switch ( $feature ) {
1869 case 'custom-header-uploads' :
1870 if ( ! isset( $_wp_theme_features['custom-header'] ) )
1872 add_theme_support( 'custom-header', array( 'uploads' => false ) );
1873 return; // Do not continue - custom-header-uploads no longer exists.
1876 if ( ! isset( $_wp_theme_features[ $feature ] ) )
1879 switch ( $feature ) {
1880 case 'custom-header' :
1881 if ( ! did_action( 'wp_loaded' ) )
1883 $support = get_theme_support( 'custom-header' );
1884 if ( $support[0]['wp-head-callback'] )
1885 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
1886 remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
1887 unset( $GLOBALS['custom_image_header'] );
1890 case 'custom-background' :
1891 if ( ! did_action( 'wp_loaded' ) )
1893 $support = get_theme_support( 'custom-background' );
1894 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
1895 remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
1896 unset( $GLOBALS['custom_background'] );
1900 unset( $_wp_theme_features[ $feature ] );
1905 * Checks a theme's support for a given feature
1909 * @global array $_wp_theme_features
1911 * @param string $feature the feature being checked
1914 function current_theme_supports( $feature ) {
1915 global $_wp_theme_features;
1917 if ( 'custom-header-uploads' == $feature )
1918 return current_theme_supports( 'custom-header', 'uploads' );
1920 if ( !isset( $_wp_theme_features[$feature] ) )
1923 // If no args passed then no extra checks need be performed
1924 if ( func_num_args() <= 1 )
1927 $args = array_slice( func_get_args(), 1 );
1929 switch ( $feature ) {
1930 case 'post-thumbnails':
1931 // post-thumbnails can be registered for only certain content/post types by passing
1932 // an array of types to add_theme_support(). If no array was passed, then
1933 // any type is accepted
1934 if ( true === $_wp_theme_features[$feature] ) // Registered for all types
1936 $content_type = $args[0];
1937 return in_array( $content_type, $_wp_theme_features[$feature][0] );
1940 case 'post-formats':
1941 // specific post formats can be registered by passing an array of types to
1942 // add_theme_support()
1944 // Specific areas of HTML5 support *must* be passed via an array to add_theme_support()
1947 return in_array( $type, $_wp_theme_features[$feature][0] );
1950 case 'custom-header':
1951 case 'custom-background':
1952 // Specific capabilities can be registered by passing an array to add_theme_support().
1953 return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
1957 * Filters whether the current theme supports a specific feature.
1959 * The dynamic portion of the hook name, `$feature`, refers to the specific theme
1960 * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
1961 * 'custom-header', 'menus', 'automatic-feed-links', 'html5', and `customize-selective-refresh-widgets`.
1965 * @param bool true Whether the current theme supports the given feature. Default true.
1966 * @param array $args Array of arguments for the feature.
1967 * @param string $feature The theme feature.
1969 return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[$feature] );
1973 * Checks a theme's support for a given feature before loading the functions which implement it.
1977 * @param string $feature The feature being checked.
1978 * @param string $include Path to the file.
1979 * @return bool True if the current theme supports the supplied feature, false otherwise.
1981 function require_if_theme_supports( $feature, $include ) {
1982 if ( current_theme_supports( $feature ) ) {
1983 require ( $include );
1990 * Checks an attachment being deleted to see if it's a header or background image.
1992 * If true it removes the theme modification which would be pointing at the deleted
1997 * @since 4.3.0 Also removes `header_image_data`.
1998 * @since 4.5.0 Also removes custom logo theme mods.
2000 * @param int $id The attachment id.
2002 function _delete_attachment_theme_mod( $id ) {
2003 $attachment_image = wp_get_attachment_url( $id );
2004 $header_image = get_header_image();
2005 $background_image = get_background_image();
2006 $custom_logo_id = get_theme_mod( 'custom_logo' );
2008 if ( $custom_logo_id && $custom_logo_id == $id ) {
2009 remove_theme_mod( 'custom_logo' );
2010 remove_theme_mod( 'header_text' );
2013 if ( $header_image && $header_image == $attachment_image ) {
2014 remove_theme_mod( 'header_image' );
2015 remove_theme_mod( 'header_image_data' );
2018 if ( $background_image && $background_image == $attachment_image ) {
2019 remove_theme_mod( 'background_image' );
2024 * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
2026 * See {@see 'after_switch_theme'}.
2030 function check_theme_switched() {
2031 if ( $stylesheet = get_option( 'theme_switched' ) ) {
2032 $old_theme = wp_get_theme( $stylesheet );
2034 // Prevent retrieve_widgets() from running since Customizer already called it up front
2035 if ( get_option( 'theme_switched_via_customizer' ) ) {
2036 remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
2037 update_option( 'theme_switched_via_customizer', false );
2040 if ( $old_theme->exists() ) {
2042 * Fires on the first WP load after a theme switch if the old theme still exists.
2044 * This action fires multiple times and the parameters differs
2045 * according to the context, if the old theme exists or not.
2046 * If the old theme is missing, the parameter will be the slug
2051 * @param string $old_name Old theme name.
2052 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
2054 do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
2056 /** This action is documented in wp-includes/theme.php */
2057 do_action( 'after_switch_theme', $stylesheet );
2059 flush_rewrite_rules();
2061 update_option( 'theme_switched', false );
2066 * Includes and instantiates the WP_Customize_Manager class.
2068 * Loads the Customizer at plugins_loaded when accessing the customize.php admin
2069 * page or when any request includes a wp_customize=on param, either as a GET
2070 * query var or as POST data. This param is a signal for whether to bootstrap
2071 * the Customizer when WordPress is loading, especially in the Customizer preview
2072 * or when making Customizer Ajax requests for widgets or menus.
2076 * @global WP_Customize_Manager $wp_customize
2078 function _wp_customize_include() {
2079 if ( ! ( ( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
2080 || ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) )
2085 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2086 $GLOBALS['wp_customize'] = new WP_Customize_Manager();
2090 * Adds settings for the customize-loader script.
2094 function _wp_customize_loader_settings() {
2095 $admin_origin = parse_url( admin_url() );
2096 $home_origin = parse_url( home_url() );
2097 $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
2100 'mobile' => wp_is_mobile(),
2101 'ios' => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
2105 'url' => esc_url( admin_url( 'customize.php' ) ),
2106 'isCrossDomain' => $cross_domain,
2107 'browser' => $browser,
2109 'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
2110 'mainIframeTitle' => __( 'Customizer' ),
2114 $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
2116 $wp_scripts = wp_scripts();
2117 $data = $wp_scripts->get_data( 'customize-loader', 'data' );
2119 $script = "$data\n$script";
2121 $wp_scripts->add_data( 'customize-loader', 'data', $script );
2125 * Returns a URL to load the Customizer.
2129 * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
2130 * The theme's stylesheet will be urlencoded if necessary.
2133 function wp_customize_url( $stylesheet = null ) {
2134 $url = admin_url( 'customize.php' );
2136 $url .= '?theme=' . urlencode( $stylesheet );
2137 return esc_url( $url );
2141 * Prints a script to check whether or not the Customizer is supported,
2142 * and apply either the no-customize-support or customize-support class
2145 * This function MUST be called inside the body tag.
2147 * Ideally, call this function immediately after the body tag is opened.
2148 * This prevents a flash of unstyled content.
2150 * It is also recommended that you add the "no-customize-support" class
2151 * to the body tag by default.
2155 function wp_customize_support_script() {
2156 $admin_origin = parse_url( admin_url() );
2157 $home_origin = parse_url( home_url() );
2158 $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
2161 <script type="text/javascript">
2163 var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
2165 <?php if ( $cross_domain ): ?>
2166 request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
2171 b[c] = b[c].replace( rcs, ' ' );
2172 b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
2179 * Whether the site is being previewed in the Customizer.
2183 * @global WP_Customize_Manager $wp_customize Customizer instance.
2185 * @return bool True if the site is being previewed in the Customizer, false otherwise.
2187 function is_customize_preview() {
2188 global $wp_customize;
2190 return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();