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();
1030 $header->url = get_header_image();
1032 if ( ! $header->url ) {
1036 $width = absint( $header->width );
1037 $height = absint( $header->height );
1039 $attr = wp_parse_args(
1042 'src' => $header->url,
1044 'height' => $height,
1045 'alt' => get_bloginfo( 'name' ),
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 );
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 );
1058 if ( $srcset && $sizes ) {
1059 $attr['srcset'] = $srcset;
1060 $attr['sizes'] = $sizes;
1065 $attr = array_map( 'esc_attr', $attr );
1068 foreach ( $attr as $name => $value ) {
1069 $html .= ' ' . $name . '="' . $value . '"';
1075 * Filters the markup of header images.
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.
1083 return apply_filters( 'get_header_image_tag', $html, $header, $attr );
1087 * Display the image markup for a custom header image.
1091 * @param array $attr Optional. Attributes for the image markup. Default empty.
1093 function the_header_image_tag( $attr = array() ) {
1094 echo get_header_image_tag( $attr );
1098 * Get random header image data from registered images in theme.
1104 * @global array $_wp_default_headers
1105 * @staticvar object $_wp_random_header
1109 function _get_random_header_data() {
1110 static $_wp_random_header = null;
1112 if ( empty( $_wp_random_header ) ) {
1113 global $_wp_default_headers;
1114 $header_image_mod = get_theme_mod( 'header_image', '' );
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;
1123 if ( current_theme_supports( 'custom-header', 'random-default' ) )
1124 $headers = $_wp_default_headers;
1128 if ( empty( $headers ) )
1129 return new stdClass;
1131 $_wp_random_header = (object) $headers[ array_rand( $headers ) ];
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() );
1136 return $_wp_random_header;
1140 * Get random header image url from registered images in theme.
1144 * @return string Path to header image
1146 function get_random_header_image() {
1147 $random_image = _get_random_header_data();
1148 if ( empty( $random_image->url ) )
1150 return $random_image->url;
1154 * Check if random header image is in use.
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().
1162 * @param string $type The random pool to use. any|default|uploaded
1165 function is_random_header_image( $type = 'any' ) {
1166 $header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
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 ) ) )
1172 if ( "random-$type-image" == $header_image_mod )
1174 elseif ( 'default' == $type && empty( $header_image_mod ) && '' != get_random_header_image() )
1182 * Display header image URL.
1186 function header_image() {
1187 $image = get_header_image();
1189 echo esc_url( $image );
1194 * Get the header images uploaded for the current theme.
1200 function get_uploaded_header_images() {
1201 $header_images = array();
1204 $headers = get_posts( array( 'post_type' => 'attachment', 'meta_key' => '_wp_attachment_is_custom_header', 'meta_value' => get_option('stylesheet'), 'orderby' => 'none', 'nopaging' => true ) );
1206 if ( empty( $headers ) )
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;
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 );
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'];
1226 return $header_images;
1230 * Get the header image data.
1234 * @global array $_wp_default_headers
1238 function get_custom_header() {
1239 global $_wp_default_headers;
1241 if ( is_random_header_image() ) {
1242 $data = _get_random_header_data();
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() );
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 );
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' ),
1270 return (object) wp_parse_args( $data, $default );
1274 * Register a selection of default headers to be displayed by the custom header admin UI.
1278 * @global array $_wp_default_headers
1280 * @param array $headers Array of headers keyed by a string id. The ids point to arrays containing 'url', 'thumbnail_url', and 'description' keys.
1282 function register_default_headers( $headers ) {
1283 global $_wp_default_headers;
1285 $_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
1289 * Unregister default headers.
1291 * This function must be called after register_default_headers() has already added the
1292 * header you want to remove.
1294 * @see register_default_headers()
1297 * @global array $_wp_default_headers
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.
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 ] );
1316 * Check whether a header video is set or not.
1320 * @see get_header_video_url()
1322 * @return bool Whether a header video is set or not.
1324 function has_header_video() {
1325 return (bool) get_header_video_url();
1328 /* Retrieve header video URL for custom header.
1330 * Uses a local video if present, or falls back to an external video. Returns false if there is no video.
1334 * @return string|false
1336 function get_header_video_url() {
1337 $id = absint( get_theme_mod( 'header_video' ) );
1338 $url = esc_url( get_theme_mod( 'external_header_video' ) );
1340 if ( ! $id && ! $url ) {
1345 // Get the file URL from the attachment ID.
1346 $url = wp_get_attachment_url( $id );
1349 return esc_url_raw( set_url_scheme( $url ) );
1353 * Display header video URL.
1357 function the_header_video_url() {
1358 $video = get_header_video_url();
1360 echo esc_url( $video );
1365 * Retrieve header video settings.
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() );
1378 'posterUrl' => get_header_image(),
1379 'videoUrl' => $video_url,
1380 'width' => absint( $header->width ),
1381 'height' => absint( $header->height ),
1385 'pause' => __( 'Pause' ),
1386 'play' => __( 'Play' ),
1387 'pauseSpeak' => __( 'Video is paused.'),
1388 'playSpeak' => __( 'Video is playing.'),
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'];
1398 return apply_filters( 'header_video_settings', $settings );
1402 * Check whether a custom header is set or not.
1406 * @return bool True if a custom header is set. False if not.
1408 function has_custom_header() {
1409 if ( has_header_image() || ( has_header_video() && is_header_video_active() ) ) {
1417 * Checks whether the custom header video is eligible to show on the current page.
1421 * @return bool True if the custom header video should be shown. False if not.
1423 function is_header_video_active() {
1424 if ( ! get_theme_support( 'custom-header', 'video' ) ) {
1428 $video_active_cb = get_theme_support( 'custom-header', 'video-active-callback' );
1430 if ( empty( $video_active_cb ) || ! is_callable( $video_active_cb ) ) {
1433 $show_video = call_user_func( $video_active_cb );
1437 * Modify whether the custom header video is eligible to show on the current page.
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()`.
1445 return apply_filters( 'is_header_video_active', $show_video );
1449 * Retrieve the markup for a custom header.
1451 * The container div will always be returned in the Customizer preview.
1455 * @return string The markup for a custom header on success.
1457 function get_custom_header_markup() {
1458 if ( ! has_custom_header() && ! is_customize_preview() ) {
1463 '<div id="wp-custom-header" class="wp-custom-header">%s</div>',
1464 get_header_image_tag()
1469 * Print the markup for a custom header.
1471 * A container div will always be printed in the Customizer preview.
1475 function the_custom_header_markup() {
1476 $custom_header = get_custom_header_markup();
1477 if ( empty( $custom_header ) ) {
1481 echo $custom_header;
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() );
1490 * Retrieve background image for custom background.
1496 function get_background_image() {
1497 return get_theme_mod('background_image', get_theme_support( 'custom-background', 'default-image' ) );
1501 * Display background image path.
1505 function background_image() {
1506 echo get_background_image();
1510 * Retrieve value for custom background color.
1516 function get_background_color() {
1517 return get_theme_mod('background_color', get_theme_support( 'custom-background', 'default-color' ) );
1521 * Display background color value.
1525 function background_color() {
1526 echo get_background_color();
1530 * Default custom background callback.
1535 function _custom_background_cb() {
1536 // $background is the saved custom image, or the default image.
1537 $background = set_url_scheme( get_background_image() );
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();
1543 if ( $color === get_theme_support( 'custom-background', 'default-color' ) ) {
1547 if ( ! $background && ! $color ) {
1548 if ( is_customize_preview() ) {
1549 echo '<style type="text/css" id="custom-background-css"></style>';
1554 $style = $color ? "background-color: #$color;" : '';
1556 if ( $background ) {
1557 $image = ' background-image: url("' . esc_url_raw( $background ) . '");';
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' ) );
1563 if ( ! in_array( $position_x, array( 'left', 'center', 'right' ), true ) ) {
1564 $position_x = 'left';
1567 if ( ! in_array( $position_y, array( 'top', 'center', 'bottom' ), true ) ) {
1568 $position_y = 'top';
1571 $position = " background-position: $position_x $position_y;";
1574 $size = get_theme_mod( 'background_size', get_theme_support( 'custom-background', 'default-size' ) );
1576 if ( ! in_array( $size, array( 'auto', 'contain', 'cover' ), true ) ) {
1580 $size = " background-size: $size;";
1582 // Background Repeat.
1583 $repeat = get_theme_mod( 'background_repeat', get_theme_support( 'custom-background', 'default-repeat' ) );
1585 if ( ! in_array( $repeat, array( 'repeat-x', 'repeat-y', 'repeat', 'no-repeat' ), true ) ) {
1589 $repeat = " background-repeat: $repeat;";
1591 // Background Scroll.
1592 $attachment = get_theme_mod( 'background_attachment', get_theme_support( 'custom-background', 'default-attachment' ) );
1594 if ( 'fixed' !== $attachment ) {
1595 $attachment = 'scroll';
1598 $attachment = " background-attachment: $attachment;";
1600 $style .= $image . $position . $size . $repeat . $attachment;
1603 <style type="text/css" id="custom-background-css">
1604 body.custom-background { <?php echo trim( $style ); ?> }
1610 * Render the Custom CSS style element.
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 > span` is not interpreted properly. ?>
1625 * Fetch the `custom_css` post for a given theme.
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.
1633 function wp_get_custom_css_post( $stylesheet = '' ) {
1634 if ( empty( $stylesheet ) ) {
1635 $stylesheet = get_stylesheet();
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,
1651 if ( get_stylesheet() === $stylesheet ) {
1652 $post_id = get_theme_mod( 'custom_css_post_id' );
1654 if ( $post_id > 0 && get_post( $post_id ) ) {
1655 $post = get_post( $post_id );
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;
1663 * Cache the lookup. See wp_update_custom_css_post().
1664 * @todo This should get cleared if a custom_css post is added/removed.
1666 set_theme_mod( 'custom_css_post_id', $post ? $post->ID : -1 );
1669 $query = new WP_Query( $custom_css_query_vars );
1670 $post = $query->post;
1677 * Fetch the saved Custom CSS content for rendering.
1682 * @param string $stylesheet Optional. A theme object stylesheet name. Defaults to the current theme.
1683 * @return string The Custom CSS Post content.
1685 function wp_get_custom_css( $stylesheet = '' ) {
1688 if ( empty( $stylesheet ) ) {
1689 $stylesheet = get_stylesheet();
1692 $post = wp_get_custom_css_post( $stylesheet );
1694 $css = $post->post_content;
1698 * Modify the Custom CSS Output into the <head>.
1702 * @param string $css CSS pulled in from the Custom CSS CPT.
1703 * @param string $stylesheet The theme stylesheet name.
1705 $css = apply_filters( 'wp_get_custom_css', $css, $stylesheet );
1711 * Update the `custom_css` post for a given theme.
1713 * Inserts a `custom_css` post when one doesn't yet exist.
1718 * @param string $css CSS, stored in `post_content`.
1719 * @param array $args {
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.
1725 * @return WP_Post|WP_Error Post on success, error on failure.
1727 function wp_update_custom_css_post( $css, $args = array() ) {
1728 $args = wp_parse_args( $args, array(
1729 'preprocessed' => '',
1730 'stylesheet' => get_stylesheet(),
1735 'preprocessed' => $args['preprocessed'],
1739 * Filters the `css` (`post_content`) and `preprocessed` (`post_content_filtered`) args for a `custom_css` post being updated.
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:
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;
1757 * @param array $data {
1760 * @type string $css CSS stored in `post_content`.
1761 * @type string $preprocessed Pre-processed CSS stored in `post_content_filtered`. Normally empty string.
1763 * @param array $args {
1764 * The args passed into `wp_update_custom_css_post()` merged with defaults.
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.
1771 $data = apply_filters( 'update_custom_css_data', $data, array_merge( $args, compact( 'css' ) ) );
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'],
1782 // Update post if it already exists, otherwise create a new one.
1783 $post = wp_get_custom_css_post( $args['stylesheet'] );
1785 $post_data['ID'] = $post->ID;
1786 $r = wp_update_post( wp_slash( $post_data ), true );
1788 $r = wp_insert_post( wp_slash( $post_data ), true );
1790 if ( ! is_wp_error( $r ) ) {
1791 if ( get_stylesheet() === $args['stylesheet'] ) {
1792 set_theme_mod( 'custom_css_post_id', $r );
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 );
1802 if ( is_wp_error( $r ) ) {
1805 return get_post( $r );
1809 * Add callback for custom TinyMCE editor stylesheets.
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'.
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.
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.
1825 * @global array $editor_styles
1827 * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
1828 * Defaults to 'editor-style.css'
1830 function add_editor_style( $stylesheet = 'editor-style.css' ) {
1831 add_theme_support( 'editor-style' );
1836 global $editor_styles;
1837 $editor_styles = (array) $editor_styles;
1838 $stylesheet = (array) $stylesheet;
1840 $rtl_stylesheet = str_replace('.css', '-rtl.css', $stylesheet[0]);
1841 $stylesheet[] = $rtl_stylesheet;
1844 $editor_styles = array_merge( $editor_styles, $stylesheet );
1848 * Removes all visual editor stylesheets.
1852 * @global array $editor_styles
1854 * @return bool True on success, false if there were no stylesheets to remove.
1856 function remove_editor_styles() {
1857 if ( ! current_theme_supports( 'editor-style' ) )
1859 _remove_theme_support( 'editor-style' );
1861 $GLOBALS['editor_styles'] = array();
1866 * Retrieve any registered editor stylesheets
1870 * @global array $editor_styles Registered editor stylesheets
1872 * @return array If registered, a list of editor stylesheet URLs.
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'];
1880 $editor_styles = array_unique( array_filter( $editor_styles ) );
1881 $style_uri = get_stylesheet_directory_uri();
1882 $style_dir = get_stylesheet_directory();
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 ] );
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();
1897 foreach ( $editor_styles as $key => $file ) {
1898 if ( $file && file_exists( "$template_dir/$file" ) ) {
1899 $stylesheets[] = "$template_uri/$file";
1904 foreach ( $editor_styles as $file ) {
1905 if ( $file && file_exists( "$style_dir/$file" ) ) {
1906 $stylesheets[] = "$style_uri/$file";
1912 * Filters the array of stylesheets applied to the editor.
1916 * @param array $stylesheets Array of stylesheets to be applied to the editor.
1918 return apply_filters( 'editor_stylesheets', $stylesheets );
1922 * Expand a theme's starter content configuration using core-provided data.
1926 * @return array Array of starter content.
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];
1936 $core_content = 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—Friday: 9:00AM–5:00PM', 'Theme starter content' ) . '<br />' . _x( 'Saturday & Sunday: 11:00AM–3:00PM', 'Theme starter content' ) . '</p>'
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' ),
1951 'archives' => array( 'archives', array(
1952 'title' => _x( 'Archives', 'Theme starter content' ),
1954 'calendar' => array( 'calendar', array(
1955 'title' => _x( 'Calendar', 'Theme starter content' ),
1957 'categories' => array( 'categories', array(
1958 'title' => _x( 'Categories', 'Theme starter content' ),
1960 'meta' => array( 'meta', array(
1961 'title' => _x( 'Meta', 'Theme starter content' ),
1963 'recent-comments' => array( 'recent-comments', array(
1964 'title' => _x( 'Recent Comments', 'Theme starter content' ),
1966 'recent-posts' => array( 'recent-posts', array(
1967 'title' => _x( 'Recent Posts', 'Theme starter content' ),
1969 'search' => array( 'search', array(
1970 'title' => _x( 'Search', 'Theme starter content' ),
1973 'nav_menus' => array(
1974 'link_home' => array(
1976 'title' => _x( 'Home', 'Theme starter content' ),
1977 'url' => home_url(),
1979 'page_home' => array( // Deprecated in favor of home_link.
1980 'type' => 'post_type',
1982 'object_id' => '{{home}}',
1984 'page_about' => array(
1985 'type' => 'post_type',
1987 'object_id' => '{{about}}',
1989 'page_blog' => array(
1990 'type' => 'post_type',
1992 'object_id' => '{{blog}}',
1994 'page_news' => array(
1995 'type' => 'post_type',
1997 'object_id' => '{{news}}',
1999 'page_contact' => array(
2000 'type' => 'post_type',
2002 'object_id' => '{{contact}}',
2005 'link_email' => array(
2006 'title' => _x( 'Email', 'Theme starter content' ),
2007 'url' => 'mailto:wordpress@example.com',
2009 'link_facebook' => array(
2010 'title' => _x( 'Facebook', 'Theme starter content' ),
2011 'url' => 'https://www.facebook.com/wordpress',
2013 'link_foursquare' => array(
2014 'title' => _x( 'Foursquare', 'Theme starter content' ),
2015 'url' => 'https://foursquare.com/',
2017 'link_github' => array(
2018 'title' => _x( 'GitHub', 'Theme starter content' ),
2019 'url' => 'https://github.com/wordpress/',
2021 'link_instagram' => array(
2022 'title' => _x( 'Instagram', 'Theme starter content' ),
2023 'url' => 'https://www.instagram.com/explore/tags/wordcamp/',
2025 'link_linkedin' => array(
2026 'title' => _x( 'LinkedIn', 'Theme starter content' ),
2027 'url' => 'https://www.linkedin.com/company/1089783',
2029 'link_pinterest' => array(
2030 'title' => _x( 'Pinterest', 'Theme starter content' ),
2031 'url' => 'https://www.pinterest.com/',
2033 'link_twitter' => array(
2034 'title' => _x( 'Twitter', 'Theme starter content' ),
2035 'url' => 'https://twitter.com/wordpress',
2037 'link_yelp' => array(
2038 'title' => _x( 'Yelp', 'Theme starter content' ),
2039 'url' => 'https://www.yelp.com',
2041 'link_youtube' => array(
2042 'title' => _x( 'YouTube', 'Theme starter content' ),
2043 'url' => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
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' ),
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’re a business with a mission to describe.', 'Theme starter content' ),
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' ),
2063 'post_type' => 'page',
2064 'post_title' => _x( 'Blog', 'Theme starter content' ),
2067 'post_type' => 'page',
2068 'post_title' => _x( 'News', 'Theme starter content' ),
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' ),
2081 foreach ( $config as $type => $args ) {
2083 // Use options and theme_mods as-is.
2086 $content[ $type ] = $config[ $type ];
2089 // Widgets are grouped into sidebars.
2091 foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
2092 foreach ( $widgets as $id => $widget ) {
2093 if ( is_array( $widget ) ) {
2095 // Item extends core content.
2096 if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2098 $core_content[ $type ][ $id ][0],
2099 array_merge( $core_content[ $type ][ $id ][1], $widget ),
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 ];
2111 // And nav menu items are grouped into nav menus.
2113 foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
2115 // Ensure nav menus get a name.
2116 if ( empty( $nav_menu['name'] ) ) {
2117 $nav_menu['name'] = $nav_menu_location;
2120 $content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
2122 foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
2123 if ( is_array( $nav_menu_item ) ) {
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 );
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 ];
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;
2147 // All that's left now are posts (besides attachments). Not a default case for the sake of clarity and future work.
2149 foreach ( $config[ $type ] as $id => $item ) {
2150 if ( is_array( $item ) ) {
2152 // Item extends core content.
2153 if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2154 $item = array_merge( $core_content[ $type ][ $id ], $item );
2157 // Enforce a subset of fields.
2158 $content[ $type ][ $id ] = wp_array_slice_assoc(
2172 } elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
2173 $content[ $type ][ $item ] = $core_content[ $type ][ $item ];
2181 * Filters the expanded array of starter content.
2185 * @param array $content Array of starter content.
2186 * @param array $config Array of theme-specific starter content configuration.
2188 return apply_filters( 'get_theme_starter_content', $content, $config );
2192 * Registers theme support for a given feature.
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.
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
2205 * @global array $_wp_theme_features
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.
2213 function add_theme_support( $feature ) {
2214 global $_wp_theme_features;
2216 if ( func_num_args() == 1 )
2219 $args = array_slice( func_get_args(), 1 );
2221 switch ( $feature ) {
2222 case 'post-thumbnails':
2223 // All post types are already supported.
2224 if ( true === get_theme_support( 'post-thumbnails' ) ) {
2229 * Merge post types with any that already declared their support
2230 * for post thumbnails.
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] ) );
2238 case 'post-formats' :
2239 if ( is_array( $args[0] ) ) {
2240 $post_formats = get_post_format_slugs();
2241 unset( $post_formats['standard'] );
2243 $args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
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' );
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] );
2263 if ( ! is_array( $args ) ) {
2264 $args = array( 0 => array() );
2269 'flex-width' => false,
2270 'flex-height' => false,
2271 'header-text' => '',
2273 $args[0] = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
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;
2282 case 'custom-header-uploads' :
2283 return add_theme_support( 'custom-header', array( 'uploads' => true ) );
2285 case 'custom-header' :
2286 if ( ! is_array( $args ) )
2287 $args = array( 0 => array() );
2290 'default-image' => '',
2291 'random-default' => false,
2294 'flex-height' => false,
2295 'flex-width' => false,
2296 'default-text-color' => '',
2297 'header-text' => true,
2299 'wp-head-callback' => '',
2300 'admin-head-callback' => '',
2301 'admin-preview-callback' => '',
2303 'video-active-callback' => 'is_front_page',
2306 $jit = isset( $args[0]['__jit'] );
2307 unset( $args[0]['__jit'] );
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] );
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.
2317 $args[0] = wp_parse_args( $args[0], $defaults );
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.
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'] ) );
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'] );
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'] );
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'] );
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'] );
2350 if ( $jit && ! empty( $args[0]['default-image'] ) )
2351 $args[0]['random-default'] = false;
2353 // If headers are supported, and we still don't have a defined width or height,
2354 // we have implicit flex sizes.
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;
2364 case 'custom-background' :
2365 if ( ! is_array( $args ) )
2366 $args = array( 0 => 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' => '',
2382 $jit = isset( $args[0]['__jit'] );
2383 unset( $args[0]['__jit'] );
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] );
2390 $args[0] = wp_parse_args( $args[0], $defaults );
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'] );
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'] );
2404 // Ensure that 'title-tag' is accessible in the admin.
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' );
2416 $_wp_theme_features[ $feature ] = $args;
2420 * Registers the internal custom header and background routines.
2425 * @global Custom_Image_Header $custom_image_header
2426 * @global Custom_Background $custom_background
2428 function _custom_header_background_just_in_time() {
2429 global $custom_image_header, $custom_background;
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 ) );
2435 $args = get_theme_support( 'custom-header' );
2436 if ( $args[0]['wp-head-callback'] )
2437 add_action( 'wp_head', $args[0]['wp-head-callback'] );
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'] );
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 ) );
2449 $args = get_theme_support( 'custom-background' );
2450 add_action( 'wp_head', $args[0]['wp-head-callback'] );
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'] );
2460 * Adds CSS to hide header text for custom logo, based on Customizer setting.
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 );
2472 <!-- Custom Logo: hide header text -->
2473 <style id="custom-logo-css" type="text/css">
2474 <?php echo $classes; ?> {
2476 clip: rect(1px, 1px, 1px, 1px);
2484 * Gets the theme support arguments passed when registering that support
2488 * @global array $_wp_theme_features
2490 * @param string $feature the feature to check
2491 * @return mixed The array of extra arguments or the value for the registered feature.
2493 function get_theme_support( $feature ) {
2494 global $_wp_theme_features;
2495 if ( ! isset( $_wp_theme_features[ $feature ] ) )
2498 if ( func_num_args() <= 1 )
2499 return $_wp_theme_features[ $feature ];
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] ];
2511 return $_wp_theme_features[ $feature ];
2516 * Allows a theme to de-register its support of a certain feature
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.
2522 * @see add_theme_support()
2523 * @param string $feature the feature being added
2524 * @return bool|void Whether feature was removed.
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' ) ) )
2531 return _remove_theme_support( $feature );
2535 * Do not use. Removes theme support internally, ignorant of the blacklist.
2540 * @global array $_wp_theme_features
2541 * @global Custom_Image_Header $custom_image_header
2542 * @global Custom_Background $custom_background
2544 * @param string $feature
2546 function _remove_theme_support( $feature ) {
2547 global $_wp_theme_features;
2549 switch ( $feature ) {
2550 case 'custom-header-uploads' :
2551 if ( ! isset( $_wp_theme_features['custom-header'] ) )
2553 add_theme_support( 'custom-header', array( 'uploads' => false ) );
2554 return; // Do not continue - custom-header-uploads no longer exists.
2557 if ( ! isset( $_wp_theme_features[ $feature ] ) )
2560 switch ( $feature ) {
2561 case 'custom-header' :
2562 if ( ! did_action( 'wp_loaded' ) )
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'] );
2568 if ( isset( $GLOBALS['custom_image_header'] ) ) {
2569 remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
2570 unset( $GLOBALS['custom_image_header'] );
2574 case 'custom-background' :
2575 if ( ! did_action( 'wp_loaded' ) )
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'] );
2584 unset( $_wp_theme_features[ $feature ] );
2589 * Checks a theme's support for a given feature
2593 * @global array $_wp_theme_features
2595 * @param string $feature the feature being checked
2598 function current_theme_supports( $feature ) {
2599 global $_wp_theme_features;
2601 if ( 'custom-header-uploads' == $feature )
2602 return current_theme_supports( 'custom-header', 'uploads' );
2604 if ( !isset( $_wp_theme_features[$feature] ) )
2607 // If no args passed then no extra checks need be performed
2608 if ( func_num_args() <= 1 )
2611 $args = array_slice( func_get_args(), 1 );
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
2620 $content_type = $args[0];
2621 return in_array( $content_type, $_wp_theme_features[$feature][0] );
2624 case 'post-formats':
2625 // specific post formats can be registered by passing an array of types to
2626 // add_theme_support()
2628 // Specific areas of HTML5 support *must* be passed via an array to add_theme_support()
2631 return in_array( $type, $_wp_theme_features[$feature][0] );
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] ] );
2641 * Filters whether the current theme supports a specific feature.
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'.
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.
2654 return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[$feature] );
2658 * Checks a theme's support for a given feature before loading the functions which implement it.
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.
2666 function require_if_theme_supports( $feature, $include ) {
2667 if ( current_theme_supports( $feature ) ) {
2668 require ( $include );
2675 * Checks an attachment being deleted to see if it's a header or background image.
2677 * If true it removes the theme modification which would be pointing at the deleted
2682 * @since 4.3.0 Also removes `header_image_data`.
2683 * @since 4.5.0 Also removes custom logo theme mods.
2685 * @param int $id The attachment id.
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' );
2693 if ( $custom_logo_id && $custom_logo_id == $id ) {
2694 remove_theme_mod( 'custom_logo' );
2695 remove_theme_mod( 'header_text' );
2698 if ( $header_image && $header_image == $attachment_image ) {
2699 remove_theme_mod( 'header_image' );
2700 remove_theme_mod( 'header_image_data' );
2703 if ( $background_image && $background_image == $attachment_image ) {
2704 remove_theme_mod( 'background_image' );
2709 * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
2711 * See {@see 'after_switch_theme'}.
2715 function check_theme_switched() {
2716 if ( $stylesheet = get_option( 'theme_switched' ) ) {
2717 $old_theme = wp_get_theme( $stylesheet );
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 );
2725 if ( $old_theme->exists() ) {
2727 * Fires on the first WP load after a theme switch if the old theme still exists.
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
2736 * @param string $old_name Old theme name.
2737 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
2739 do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
2741 /** This action is documented in wp-includes/theme.php */
2742 do_action( 'after_switch_theme', $stylesheet );
2744 flush_rewrite_rules();
2746 update_option( 'theme_switched', false );
2751 * Includes and instantiates the WP_Customize_Manager class.
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.
2761 * @global WP_Customize_Manager $wp_customize
2763 function _wp_customize_include() {
2765 $is_customize_admin_page = ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) );
2767 $is_customize_admin_page
2769 ( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
2771 ( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
2774 if ( ! $should_include ) {
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.
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 )
2790 $changeset_uuid = null;
2791 $messenger_channel = null;
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'] );
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'];
2805 if ( isset( $input_vars['customize_messenger_channel'] ) ) {
2806 $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
2809 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2810 $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel' ) );
2814 * Publish a snapshot's changes.
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.
2820 function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
2821 global $wp_customize, $wpdb;
2823 $is_publishing_changeset = (
2824 'customize_changeset' === $changeset_post->post_type
2826 'publish' === $new_status
2828 'publish' !== $old_status
2830 if ( ! $is_publishing_changeset ) {
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 ) );
2839 if ( ! did_action( 'customize_register' ) ) {
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.
2854 remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
2855 $wp_customize->register_controls();
2857 /** This filter is documented in /wp-includes/class-wp-customize-manager.php */
2858 do_action( 'customize_register', $wp_customize );
2860 $wp_customize->_publish_changeset_values( $changeset_post->ID ) ;
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.
2871 if ( ! wp_revisions_enabled( $changeset_post ) ) {
2872 $post = $changeset_post;
2873 $post_id = $changeset_post->ID;
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
2881 if ( ! EMPTY_TRASH_DAYS ) {
2882 wp_delete_post( $post_id, true );
2884 /** This action is documented in wp-includes/post.php */
2885 do_action( 'wp_trash_post', $post_id );
2887 add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
2888 add_post_meta( $post_id, '_wp_trash_meta_time', time() );
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 );
2895 $post->post_status = $new_status;
2896 wp_transition_post_status( $new_status, $old_status, $post );
2898 /** This action is documented in wp-includes/post.php */
2899 do_action( 'edit_post', $post->ID, $post );
2901 /** This action is documented in wp-includes/post.php */
2902 do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
2904 /** This action is documented in wp-includes/post.php */
2905 do_action( 'save_post', $post->ID, $post, true );
2907 /** This action is documented in wp-includes/post.php */
2908 do_action( 'wp_insert_post', $post->ID, $post, true );
2910 /** This action is documented in wp-includes/post.php */
2911 do_action( 'trashed_post', $post_id );
2917 * Filters changeset post data upon insert to ensure post_name is intact.
2919 * This is needed to prevent the post_name from being dropped when the post is
2920 * transitioned into pending status by a contributor.
2923 * @see wp_insert_post()
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.
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'] ) {
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'];
2941 * Adds settings for the customize-loader script.
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' ] ) );
2951 'mobile' => wp_is_mobile(),
2952 'ios' => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
2956 'url' => esc_url( admin_url( 'customize.php' ) ),
2957 'isCrossDomain' => $cross_domain,
2958 'browser' => $browser,
2960 'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
2961 'mainIframeTitle' => __( 'Customizer' ),
2965 $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
2967 $wp_scripts = wp_scripts();
2968 $data = $wp_scripts->get_data( 'customize-loader', 'data' );
2970 $script = "$data\n$script";
2972 $wp_scripts->add_data( 'customize-loader', 'data', $script );
2976 * Returns a URL to load the Customizer.
2980 * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
2981 * The theme's stylesheet will be urlencoded if necessary.
2984 function wp_customize_url( $stylesheet = null ) {
2985 $url = admin_url( 'customize.php' );
2987 $url .= '?theme=' . urlencode( $stylesheet );
2988 return esc_url( $url );
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
2996 * This function MUST be called inside the body tag.
2998 * Ideally, call this function immediately after the body tag is opened.
2999 * This prevents a flash of unstyled content.
3001 * It is also recommended that you add the "no-customize-support" class
3002 * to the body tag by default.
3005 * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
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' ] ) );
3014 <script type="text/javascript">
3015 document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, '' ) + ' no-customize-support';
3018 <!--[if gte IE 9]><!-->
3019 <script type="text/javascript">
3021 var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
3023 <?php if ( $cross_domain ) : ?>
3024 request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
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;
3039 * Whether the site is being previewed in the Customizer.
3043 * @global WP_Customize_Manager $wp_customize Customizer instance.
3045 * @return bool True if the site is being previewed in the Customizer, false otherwise.
3047 function is_customize_preview() {
3048 global $wp_customize;
3050 return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();