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(" . wp_json_encode( $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_term_meta_cache' => false,
1650 if ( get_stylesheet() === $stylesheet ) {
1651 $post_id = get_theme_mod( 'custom_css_post_id' );
1653 if ( $post_id > 0 && get_post( $post_id ) ) {
1654 $post = get_post( $post_id );
1656 $query = new WP_Query( $custom_css_query_vars );
1657 $post = $query->post;
1659 * Cache the lookup. See WP_Customize_Custom_CSS_Setting::update().
1660 * @todo This should get cleared if a custom_css post is added/removed.
1663 set_theme_mod( 'custom_css_post_id', $post->ID );
1664 } elseif ( -1 !== $post_id ) {
1665 set_theme_mod( 'custom_css_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 // Trigger creation of a revision. This should be removed once #30854 is resolved.
1791 if ( ! is_wp_error( $r ) && 0 === count( wp_get_post_revisions( $r ) ) ) {
1792 wp_save_post_revision( $r );
1796 if ( is_wp_error( $r ) ) {
1799 return get_post( $r );
1803 * Add callback for custom TinyMCE editor stylesheets.
1805 * The parameter $stylesheet is the name of the stylesheet, relative to
1806 * the theme root. It also accepts an array of stylesheets.
1807 * It is optional and defaults to 'editor-style.css'.
1809 * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
1810 * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
1811 * If an array of stylesheets is passed to add_editor_style(),
1812 * RTL is only added for the first stylesheet.
1814 * Since version 3.4 the TinyMCE body has .rtl CSS class.
1815 * It is a better option to use that class and add any RTL styles to the main stylesheet.
1819 * @global array $editor_styles
1821 * @param array|string $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
1822 * Defaults to 'editor-style.css'
1824 function add_editor_style( $stylesheet = 'editor-style.css' ) {
1825 add_theme_support( 'editor-style' );
1830 global $editor_styles;
1831 $editor_styles = (array) $editor_styles;
1832 $stylesheet = (array) $stylesheet;
1834 $rtl_stylesheet = str_replace('.css', '-rtl.css', $stylesheet[0]);
1835 $stylesheet[] = $rtl_stylesheet;
1838 $editor_styles = array_merge( $editor_styles, $stylesheet );
1842 * Removes all visual editor stylesheets.
1846 * @global array $editor_styles
1848 * @return bool True on success, false if there were no stylesheets to remove.
1850 function remove_editor_styles() {
1851 if ( ! current_theme_supports( 'editor-style' ) )
1853 _remove_theme_support( 'editor-style' );
1855 $GLOBALS['editor_styles'] = array();
1860 * Retrieve any registered editor stylesheets
1864 * @global array $editor_styles Registered editor stylesheets
1866 * @return array If registered, a list of editor stylesheet URLs.
1868 function get_editor_stylesheets() {
1869 $stylesheets = array();
1870 // load editor_style.css if the current theme supports it
1871 if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
1872 $editor_styles = $GLOBALS['editor_styles'];
1874 $editor_styles = array_unique( array_filter( $editor_styles ) );
1875 $style_uri = get_stylesheet_directory_uri();
1876 $style_dir = get_stylesheet_directory();
1878 // Support externally referenced styles (like, say, fonts).
1879 foreach ( $editor_styles as $key => $file ) {
1880 if ( preg_match( '~^(https?:)?//~', $file ) ) {
1881 $stylesheets[] = esc_url_raw( $file );
1882 unset( $editor_styles[ $key ] );
1886 // Look in a parent theme first, that way child theme CSS overrides.
1887 if ( is_child_theme() ) {
1888 $template_uri = get_template_directory_uri();
1889 $template_dir = get_template_directory();
1891 foreach ( $editor_styles as $key => $file ) {
1892 if ( $file && file_exists( "$template_dir/$file" ) ) {
1893 $stylesheets[] = "$template_uri/$file";
1898 foreach ( $editor_styles as $file ) {
1899 if ( $file && file_exists( "$style_dir/$file" ) ) {
1900 $stylesheets[] = "$style_uri/$file";
1906 * Filters the array of stylesheets applied to the editor.
1910 * @param array $stylesheets Array of stylesheets to be applied to the editor.
1912 return apply_filters( 'editor_stylesheets', $stylesheets );
1916 * Expand a theme's starter content configuration using core-provided data.
1920 * @return array Array of starter content.
1922 function get_theme_starter_content() {
1923 $theme_support = get_theme_support( 'starter-content' );
1924 if ( is_array( $theme_support ) && ! empty( $theme_support[0] ) && is_array( $theme_support[0] ) ) {
1925 $config = $theme_support[0];
1930 $core_content = array(
1932 'text_business_info' => array( 'text', array(
1933 'title' => _x( 'Find Us', 'Theme starter content' ),
1934 'text' => join( '', array(
1935 '<p><strong>' . _x( 'Address', 'Theme starter content' ) . '</strong><br />',
1936 _x( '123 Main Street', 'Theme starter content' ) . '<br />' . _x( 'New York, NY 10001', 'Theme starter content' ) . '</p>',
1937 '<p><strong>' . _x( 'Hours', 'Theme starter content' ) . '</strong><br />',
1938 _x( 'Monday—Friday: 9:00AM–5:00PM', 'Theme starter content' ) . '<br />' . _x( 'Saturday & Sunday: 11:00AM–3:00PM', 'Theme starter content' ) . '</p>'
1941 'text_about' => array( 'text', array(
1942 'title' => _x( 'About This Site', 'Theme starter content' ),
1943 'text' => _x( 'This may be a good place to introduce yourself and your site or include some credits.', 'Theme starter content' ),
1945 'archives' => array( 'archives', array(
1946 'title' => _x( 'Archives', 'Theme starter content' ),
1948 'calendar' => array( 'calendar', array(
1949 'title' => _x( 'Calendar', 'Theme starter content' ),
1951 'categories' => array( 'categories', array(
1952 'title' => _x( 'Categories', 'Theme starter content' ),
1954 'meta' => array( 'meta', array(
1955 'title' => _x( 'Meta', 'Theme starter content' ),
1957 'recent-comments' => array( 'recent-comments', array(
1958 'title' => _x( 'Recent Comments', 'Theme starter content' ),
1960 'recent-posts' => array( 'recent-posts', array(
1961 'title' => _x( 'Recent Posts', 'Theme starter content' ),
1963 'search' => array( 'search', array(
1964 'title' => _x( 'Search', 'Theme starter content' ),
1967 'nav_menus' => array(
1968 'page_home' => array(
1969 'type' => 'post_type',
1971 'object_id' => '{{home}}',
1973 'page_about' => array(
1974 'type' => 'post_type',
1976 'object_id' => '{{about}}',
1978 'page_blog' => array(
1979 'type' => 'post_type',
1981 'object_id' => '{{blog}}',
1983 'page_news' => array(
1984 'type' => 'post_type',
1986 'object_id' => '{{news}}',
1988 'page_contact' => array(
1989 'type' => 'post_type',
1991 'object_id' => '{{contact}}',
1994 'link_email' => array(
1995 'title' => _x( 'Email', 'Theme starter content' ),
1996 'url' => 'mailto:wordpress@example.com',
1998 'link_facebook' => array(
1999 'title' => _x( 'Facebook', 'Theme starter content' ),
2000 'url' => 'https://www.facebook.com/wordpress',
2002 'link_foursquare' => array(
2003 'title' => _x( 'Foursquare', 'Theme starter content' ),
2004 'url' => 'https://foursquare.com/',
2006 'link_github' => array(
2007 'title' => _x( 'GitHub', 'Theme starter content' ),
2008 'url' => 'https://github.com/wordpress/',
2010 'link_instagram' => array(
2011 'title' => _x( 'Instagram', 'Theme starter content' ),
2012 'url' => 'https://www.instagram.com/explore/tags/wordcamp/',
2014 'link_linkedin' => array(
2015 'title' => _x( 'LinkedIn', 'Theme starter content' ),
2016 'url' => 'https://www.linkedin.com/company/1089783',
2018 'link_pinterest' => array(
2019 'title' => _x( 'Pinterest', 'Theme starter content' ),
2020 'url' => 'https://www.pinterest.com/',
2022 'link_twitter' => array(
2023 'title' => _x( 'Twitter', 'Theme starter content' ),
2024 'url' => 'https://twitter.com/wordpress',
2026 'link_yelp' => array(
2027 'title' => _x( 'Yelp', 'Theme starter content' ),
2028 'url' => 'https://www.yelp.com',
2030 'link_youtube' => array(
2031 'title' => _x( 'YouTube', 'Theme starter content' ),
2032 'url' => 'https://www.youtube.com/channel/UCdof4Ju7amm1chz1gi1T2ZA',
2037 'post_type' => 'page',
2038 'post_title' => _x( 'Home', 'Theme starter content' ),
2039 'post_content' => _x( 'Welcome to your site! This is your homepage, which is what most visitors will see when they come to your site for the first time.', 'Theme starter content' ),
2042 'post_type' => 'page',
2043 'post_title' => _x( 'About', 'Theme starter content' ),
2044 'post_content' => _x( 'You might be an artist who would like to introduce yourself and your work here or maybe you’re a business with a mission to describe.', 'Theme starter content' ),
2047 'post_type' => 'page',
2048 'post_title' => _x( 'Contact', 'Theme starter content' ),
2049 'post_content' => _x( 'This is a page with some basic contact information, such as an address and phone number. You might also try a plugin to add a contact form.', 'Theme starter content' ),
2052 'post_type' => 'page',
2053 'post_title' => _x( 'Blog', 'Theme starter content' ),
2056 'post_type' => 'page',
2057 'post_title' => _x( 'News', 'Theme starter content' ),
2060 'homepage-section' => array(
2061 'post_type' => 'page',
2062 'post_title' => _x( 'A homepage section', 'Theme starter content' ),
2063 'post_content' => _x( 'This is an example of a homepage section. Homepage sections can be any page other than the homepage itself, including the page that shows your latest blog posts.', 'Theme starter content' ),
2070 foreach ( $config as $type => $args ) {
2072 // Use options and theme_mods as-is.
2075 $content[ $type ] = $config[ $type ];
2078 // Widgets are grouped into sidebars.
2080 foreach ( $config[ $type ] as $sidebar_id => $widgets ) {
2081 foreach ( $widgets as $id => $widget ) {
2082 if ( is_array( $widget ) ) {
2084 // Item extends core content.
2085 if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2087 $core_content[ $type ][ $id ][0],
2088 array_merge( $core_content[ $type ][ $id ][1], $widget ),
2092 $content[ $type ][ $sidebar_id ][] = $widget;
2093 } elseif ( is_string( $widget ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $widget ] ) ) {
2094 $content[ $type ][ $sidebar_id ][] = $core_content[ $type ][ $widget ];
2100 // And nav menu items are grouped into nav menus.
2102 foreach ( $config[ $type ] as $nav_menu_location => $nav_menu ) {
2104 // Ensure nav menus get a name.
2105 if ( empty( $nav_menu['name'] ) ) {
2106 $nav_menu['name'] = $nav_menu_location;
2109 $content[ $type ][ $nav_menu_location ]['name'] = $nav_menu['name'];
2111 foreach ( $nav_menu['items'] as $id => $nav_menu_item ) {
2112 if ( is_array( $nav_menu_item ) ) {
2114 // Item extends core content.
2115 if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2116 $nav_menu_item = array_merge( $core_content[ $type ][ $id ], $nav_menu_item );
2119 $content[ $type ][ $nav_menu_location ]['items'][] = $nav_menu_item;
2120 } elseif ( is_string( $nav_menu_item ) && ! empty( $core_content[ $type ] ) && ! empty( $core_content[ $type ][ $nav_menu_item ] ) ) {
2121 $content[ $type ][ $nav_menu_location ]['items'][] = $core_content[ $type ][ $nav_menu_item ];
2127 // Attachments are posts but have special treatment.
2128 case 'attachments' :
2129 foreach ( $config[ $type ] as $id => $item ) {
2130 if ( ! empty( $item['file'] ) ) {
2131 $content[ $type ][ $id ] = $item;
2136 // All that's left now are posts (besides attachments). Not a default case for the sake of clarity and future work.
2138 foreach ( $config[ $type ] as $id => $item ) {
2139 if ( is_array( $item ) ) {
2141 // Item extends core content.
2142 if ( ! empty( $core_content[ $type ][ $id ] ) ) {
2143 $item = array_merge( $core_content[ $type ][ $id ], $item );
2146 // Enforce a subset of fields.
2147 $content[ $type ][ $id ] = wp_array_slice_assoc(
2161 } elseif ( is_string( $item ) && ! empty( $core_content[ $type ][ $item ] ) ) {
2162 $content[ $type ][ $item ] = $core_content[ $type ][ $item ];
2170 * Filters the expanded array of starter content.
2174 * @param array $content Array of starter content.
2175 * @param array $config Array of theme-specific starter content configuration.
2177 return apply_filters( 'get_theme_starter_content', $content, $config );
2181 * Registers theme support for a given feature.
2183 * Must be called in the theme's functions.php file to work.
2184 * If attached to a hook, it must be {@see 'after_setup_theme'}.
2185 * The {@see 'init'} hook may be too late for some features.
2188 * @since 3.6.0 The `html5` feature was added
2189 * @since 3.9.0 The `html5` feature now also accepts 'gallery' and 'caption'
2190 * @since 4.1.0 The `title-tag` feature was added
2191 * @since 4.5.0 The `customize-selective-refresh-widgets` feature was added
2192 * @since 4.7.0 The `starter-content` feature was added
2194 * @global array $_wp_theme_features
2196 * @param string $feature The feature being added. Likely core values include 'post-formats',
2197 * 'post-thumbnails', 'html5', 'custom-logo', 'custom-header-uploads',
2198 * 'custom-header', 'custom-background', 'title-tag', 'starter-content', etc.
2199 * @param mixed $args,... Optional extra arguments to pass along with certain features.
2200 * @return void|bool False on failure, void otherwise.
2202 function add_theme_support( $feature ) {
2203 global $_wp_theme_features;
2205 if ( func_num_args() == 1 )
2208 $args = array_slice( func_get_args(), 1 );
2210 switch ( $feature ) {
2211 case 'post-thumbnails':
2212 // All post types are already supported.
2213 if ( true === get_theme_support( 'post-thumbnails' ) ) {
2218 * Merge post types with any that already declared their support
2219 * for post thumbnails.
2221 if ( is_array( $args[0] ) && isset( $_wp_theme_features['post-thumbnails'] ) ) {
2222 $args[0] = array_unique( array_merge( $_wp_theme_features['post-thumbnails'][0], $args[0] ) );
2227 case 'post-formats' :
2228 if ( is_array( $args[0] ) ) {
2229 $post_formats = get_post_format_slugs();
2230 unset( $post_formats['standard'] );
2232 $args[0] = array_intersect( $args[0], array_keys( $post_formats ) );
2237 // You can't just pass 'html5', you need to pass an array of types.
2238 if ( empty( $args[0] ) ) {
2239 // Build an array of types for back-compat.
2240 $args = array( 0 => array( 'comment-list', 'comment-form', 'search-form' ) );
2241 } elseif ( ! is_array( $args[0] ) ) {
2242 _doing_it_wrong( "add_theme_support( 'html5' )", __( 'You need to pass an array of types.' ), '3.6.1' );
2246 // Calling 'html5' again merges, rather than overwrites.
2247 if ( isset( $_wp_theme_features['html5'] ) )
2248 $args[0] = array_merge( $_wp_theme_features['html5'][0], $args[0] );
2252 if ( ! is_array( $args ) ) {
2253 $args = array( 0 => array() );
2258 'flex-width' => false,
2259 'flex-height' => false,
2260 'header-text' => '',
2262 $args[0] = wp_parse_args( array_intersect_key( $args[0], $defaults ), $defaults );
2264 // Allow full flexibility if no size is specified.
2265 if ( is_null( $args[0]['width'] ) && is_null( $args[0]['height'] ) ) {
2266 $args[0]['flex-width'] = true;
2267 $args[0]['flex-height'] = true;
2271 case 'custom-header-uploads' :
2272 return add_theme_support( 'custom-header', array( 'uploads' => true ) );
2274 case 'custom-header' :
2275 if ( ! is_array( $args ) )
2276 $args = array( 0 => array() );
2279 'default-image' => '',
2280 'random-default' => false,
2283 'flex-height' => false,
2284 'flex-width' => false,
2285 'default-text-color' => '',
2286 'header-text' => true,
2288 'wp-head-callback' => '',
2289 'admin-head-callback' => '',
2290 'admin-preview-callback' => '',
2292 'video-active-callback' => 'is_front_page',
2295 $jit = isset( $args[0]['__jit'] );
2296 unset( $args[0]['__jit'] );
2298 // Merge in data from previous add_theme_support() calls.
2299 // The first value registered wins. (A child theme is set up first.)
2300 if ( isset( $_wp_theme_features['custom-header'] ) )
2301 $args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
2303 // Load in the defaults at the end, as we need to insure first one wins.
2304 // This will cause all constants to be defined, as each arg will then be set to the default.
2306 $args[0] = wp_parse_args( $args[0], $defaults );
2308 // If a constant was defined, use that value. Otherwise, define the constant to ensure
2309 // the constant is always accurate (and is not defined later, overriding our value).
2310 // As stated above, the first value wins.
2311 // Once we get to wp_loaded (just-in-time), define any constants we haven't already.
2312 // Constants are lame. Don't reference them. This is just for backward compatibility.
2314 if ( defined( 'NO_HEADER_TEXT' ) )
2315 $args[0]['header-text'] = ! NO_HEADER_TEXT;
2316 elseif ( isset( $args[0]['header-text'] ) )
2317 define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
2319 if ( defined( 'HEADER_IMAGE_WIDTH' ) )
2320 $args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
2321 elseif ( isset( $args[0]['width'] ) )
2322 define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
2324 if ( defined( 'HEADER_IMAGE_HEIGHT' ) )
2325 $args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
2326 elseif ( isset( $args[0]['height'] ) )
2327 define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
2329 if ( defined( 'HEADER_TEXTCOLOR' ) )
2330 $args[0]['default-text-color'] = HEADER_TEXTCOLOR;
2331 elseif ( isset( $args[0]['default-text-color'] ) )
2332 define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
2334 if ( defined( 'HEADER_IMAGE' ) )
2335 $args[0]['default-image'] = HEADER_IMAGE;
2336 elseif ( isset( $args[0]['default-image'] ) )
2337 define( 'HEADER_IMAGE', $args[0]['default-image'] );
2339 if ( $jit && ! empty( $args[0]['default-image'] ) )
2340 $args[0]['random-default'] = false;
2342 // If headers are supported, and we still don't have a defined width or height,
2343 // we have implicit flex sizes.
2345 if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) )
2346 $args[0]['flex-width'] = true;
2347 if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) )
2348 $args[0]['flex-height'] = true;
2353 case 'custom-background' :
2354 if ( ! is_array( $args ) )
2355 $args = array( 0 => array() );
2358 'default-image' => '',
2359 'default-preset' => 'default',
2360 'default-position-x' => 'left',
2361 'default-position-y' => 'top',
2362 'default-size' => 'auto',
2363 'default-repeat' => 'repeat',
2364 'default-attachment' => 'scroll',
2365 'default-color' => '',
2366 'wp-head-callback' => '_custom_background_cb',
2367 'admin-head-callback' => '',
2368 'admin-preview-callback' => '',
2371 $jit = isset( $args[0]['__jit'] );
2372 unset( $args[0]['__jit'] );
2374 // Merge in data from previous add_theme_support() calls. The first value registered wins.
2375 if ( isset( $_wp_theme_features['custom-background'] ) )
2376 $args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
2379 $args[0] = wp_parse_args( $args[0], $defaults );
2381 if ( defined( 'BACKGROUND_COLOR' ) )
2382 $args[0]['default-color'] = BACKGROUND_COLOR;
2383 elseif ( isset( $args[0]['default-color'] ) || $jit )
2384 define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
2386 if ( defined( 'BACKGROUND_IMAGE' ) )
2387 $args[0]['default-image'] = BACKGROUND_IMAGE;
2388 elseif ( isset( $args[0]['default-image'] ) || $jit )
2389 define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
2393 // Ensure that 'title-tag' is accessible in the admin.
2395 // Can be called in functions.php but must happen before wp_loaded, i.e. not in header.php.
2396 if ( did_action( 'wp_loaded' ) ) {
2397 /* translators: 1: Theme support 2: hook name */
2398 _doing_it_wrong( "add_theme_support( 'title-tag' )", sprintf( __( 'Theme support for %1$s should be registered before the %2$s hook.' ),
2399 '<code>title-tag</code>', '<code>wp_loaded</code>' ), '4.1.0' );
2405 $_wp_theme_features[ $feature ] = $args;
2409 * Registers the internal custom header and background routines.
2414 * @global Custom_Image_Header $custom_image_header
2415 * @global Custom_Background $custom_background
2417 function _custom_header_background_just_in_time() {
2418 global $custom_image_header, $custom_background;
2420 if ( current_theme_supports( 'custom-header' ) ) {
2421 // In case any constants were defined after an add_custom_image_header() call, re-run.
2422 add_theme_support( 'custom-header', array( '__jit' => true ) );
2424 $args = get_theme_support( 'custom-header' );
2425 if ( $args[0]['wp-head-callback'] )
2426 add_action( 'wp_head', $args[0]['wp-head-callback'] );
2429 require_once( ABSPATH . 'wp-admin/custom-header.php' );
2430 $custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2434 if ( current_theme_supports( 'custom-background' ) ) {
2435 // In case any constants were defined after an add_custom_background() call, re-run.
2436 add_theme_support( 'custom-background', array( '__jit' => true ) );
2438 $args = get_theme_support( 'custom-background' );
2439 add_action( 'wp_head', $args[0]['wp-head-callback'] );
2442 require_once( ABSPATH . 'wp-admin/custom-background.php' );
2443 $custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
2449 * Adds CSS to hide header text for custom logo, based on Customizer setting.
2454 function _custom_logo_header_styles() {
2455 if ( ! current_theme_supports( 'custom-header', 'header-text' ) && get_theme_support( 'custom-logo', 'header-text' ) && ! get_theme_mod( 'header_text', true ) ) {
2456 $classes = (array) get_theme_support( 'custom-logo', 'header-text' );
2457 $classes = array_map( 'sanitize_html_class', $classes );
2458 $classes = '.' . implode( ', .', $classes );
2461 <!-- Custom Logo: hide header text -->
2462 <style id="custom-logo-css" type="text/css">
2463 <?php echo $classes; ?> {
2465 clip: rect(1px, 1px, 1px, 1px);
2473 * Gets the theme support arguments passed when registering that support
2477 * @global array $_wp_theme_features
2479 * @param string $feature the feature to check
2480 * @return mixed The array of extra arguments or the value for the registered feature.
2482 function get_theme_support( $feature ) {
2483 global $_wp_theme_features;
2484 if ( ! isset( $_wp_theme_features[ $feature ] ) )
2487 if ( func_num_args() <= 1 )
2488 return $_wp_theme_features[ $feature ];
2490 $args = array_slice( func_get_args(), 1 );
2491 switch ( $feature ) {
2492 case 'custom-logo' :
2493 case 'custom-header' :
2494 case 'custom-background' :
2495 if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) )
2496 return $_wp_theme_features[ $feature ][0][ $args[0] ];
2500 return $_wp_theme_features[ $feature ];
2505 * Allows a theme to de-register its support of a certain feature
2507 * Should be called in the theme's functions.php file. Generally would
2508 * be used for child themes to override support from the parent theme.
2511 * @see add_theme_support()
2512 * @param string $feature the feature being added
2513 * @return bool|void Whether feature was removed.
2515 function remove_theme_support( $feature ) {
2516 // Blacklist: for internal registrations not used directly by themes.
2517 if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ) ) )
2520 return _remove_theme_support( $feature );
2524 * Do not use. Removes theme support internally, ignorant of the blacklist.
2529 * @global array $_wp_theme_features
2530 * @global Custom_Image_Header $custom_image_header
2531 * @global Custom_Background $custom_background
2533 * @param string $feature
2535 function _remove_theme_support( $feature ) {
2536 global $_wp_theme_features;
2538 switch ( $feature ) {
2539 case 'custom-header-uploads' :
2540 if ( ! isset( $_wp_theme_features['custom-header'] ) )
2542 add_theme_support( 'custom-header', array( 'uploads' => false ) );
2543 return; // Do not continue - custom-header-uploads no longer exists.
2546 if ( ! isset( $_wp_theme_features[ $feature ] ) )
2549 switch ( $feature ) {
2550 case 'custom-header' :
2551 if ( ! did_action( 'wp_loaded' ) )
2553 $support = get_theme_support( 'custom-header' );
2554 if ( isset( $support[0]['wp-head-callback'] ) ) {
2555 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2557 if ( isset( $GLOBALS['custom_image_header'] ) ) {
2558 remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
2559 unset( $GLOBALS['custom_image_header'] );
2563 case 'custom-background' :
2564 if ( ! did_action( 'wp_loaded' ) )
2566 $support = get_theme_support( 'custom-background' );
2567 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
2568 remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
2569 unset( $GLOBALS['custom_background'] );
2573 unset( $_wp_theme_features[ $feature ] );
2578 * Checks a theme's support for a given feature
2582 * @global array $_wp_theme_features
2584 * @param string $feature the feature being checked
2587 function current_theme_supports( $feature ) {
2588 global $_wp_theme_features;
2590 if ( 'custom-header-uploads' == $feature )
2591 return current_theme_supports( 'custom-header', 'uploads' );
2593 if ( !isset( $_wp_theme_features[$feature] ) )
2596 // If no args passed then no extra checks need be performed
2597 if ( func_num_args() <= 1 )
2600 $args = array_slice( func_get_args(), 1 );
2602 switch ( $feature ) {
2603 case 'post-thumbnails':
2604 // post-thumbnails can be registered for only certain content/post types by passing
2605 // an array of types to add_theme_support(). If no array was passed, then
2606 // any type is accepted
2607 if ( true === $_wp_theme_features[$feature] ) // Registered for all types
2609 $content_type = $args[0];
2610 return in_array( $content_type, $_wp_theme_features[$feature][0] );
2613 case 'post-formats':
2614 // specific post formats can be registered by passing an array of types to
2615 // add_theme_support()
2617 // Specific areas of HTML5 support *must* be passed via an array to add_theme_support()
2620 return in_array( $type, $_wp_theme_features[$feature][0] );
2623 case 'custom-header':
2624 case 'custom-background':
2625 // Specific capabilities can be registered by passing an array to add_theme_support().
2626 return ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) && $_wp_theme_features[ $feature ][0][ $args[0] ] );
2630 * Filters whether the current theme supports a specific feature.
2632 * The dynamic portion of the hook name, `$feature`, refers to the specific theme
2633 * feature. Possible values include 'post-formats', 'post-thumbnails', 'custom-background',
2634 * 'custom-header', 'menus', 'automatic-feed-links', 'html5',
2635 * 'starter-content', and 'customize-selective-refresh-widgets'.
2639 * @param bool true Whether the current theme supports the given feature. Default true.
2640 * @param array $args Array of arguments for the feature.
2641 * @param string $feature The theme feature.
2643 return apply_filters( "current_theme_supports-{$feature}", true, $args, $_wp_theme_features[$feature] );
2647 * Checks a theme's support for a given feature before loading the functions which implement it.
2651 * @param string $feature The feature being checked.
2652 * @param string $include Path to the file.
2653 * @return bool True if the current theme supports the supplied feature, false otherwise.
2655 function require_if_theme_supports( $feature, $include ) {
2656 if ( current_theme_supports( $feature ) ) {
2657 require ( $include );
2664 * Checks an attachment being deleted to see if it's a header or background image.
2666 * If true it removes the theme modification which would be pointing at the deleted
2671 * @since 4.3.0 Also removes `header_image_data`.
2672 * @since 4.5.0 Also removes custom logo theme mods.
2674 * @param int $id The attachment id.
2676 function _delete_attachment_theme_mod( $id ) {
2677 $attachment_image = wp_get_attachment_url( $id );
2678 $header_image = get_header_image();
2679 $background_image = get_background_image();
2680 $custom_logo_id = get_theme_mod( 'custom_logo' );
2682 if ( $custom_logo_id && $custom_logo_id == $id ) {
2683 remove_theme_mod( 'custom_logo' );
2684 remove_theme_mod( 'header_text' );
2687 if ( $header_image && $header_image == $attachment_image ) {
2688 remove_theme_mod( 'header_image' );
2689 remove_theme_mod( 'header_image_data' );
2692 if ( $background_image && $background_image == $attachment_image ) {
2693 remove_theme_mod( 'background_image' );
2698 * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load.
2700 * See {@see 'after_switch_theme'}.
2704 function check_theme_switched() {
2705 if ( $stylesheet = get_option( 'theme_switched' ) ) {
2706 $old_theme = wp_get_theme( $stylesheet );
2708 // Prevent retrieve_widgets() from running since Customizer already called it up front
2709 if ( get_option( 'theme_switched_via_customizer' ) ) {
2710 remove_action( 'after_switch_theme', '_wp_sidebars_changed' );
2711 update_option( 'theme_switched_via_customizer', false );
2714 if ( $old_theme->exists() ) {
2716 * Fires on the first WP load after a theme switch if the old theme still exists.
2718 * This action fires multiple times and the parameters differs
2719 * according to the context, if the old theme exists or not.
2720 * If the old theme is missing, the parameter will be the slug
2725 * @param string $old_name Old theme name.
2726 * @param WP_Theme $old_theme WP_Theme instance of the old theme.
2728 do_action( 'after_switch_theme', $old_theme->get( 'Name' ), $old_theme );
2730 /** This action is documented in wp-includes/theme.php */
2731 do_action( 'after_switch_theme', $stylesheet );
2733 flush_rewrite_rules();
2735 update_option( 'theme_switched', false );
2740 * Includes and instantiates the WP_Customize_Manager class.
2742 * Loads the Customizer at plugins_loaded when accessing the customize.php admin
2743 * page or when any request includes a wp_customize=on param or a customize_changeset
2744 * param (a UUID). This param is a signal for whether to bootstrap the Customizer when
2745 * WordPress is loading, especially in the Customizer preview
2746 * or when making Customizer Ajax requests for widgets or menus.
2750 * @global WP_Customize_Manager $wp_customize
2752 function _wp_customize_include() {
2754 $is_customize_admin_page = ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) );
2756 $is_customize_admin_page
2758 ( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
2760 ( ! empty( $_GET['customize_changeset_uuid'] ) || ! empty( $_POST['customize_changeset_uuid'] ) )
2763 if ( ! $should_include ) {
2768 * Note that wp_unslash() is not being used on the input vars because it is
2769 * called before wp_magic_quotes() gets called. Besides this fact, none of
2770 * the values should contain any characters needing slashes anyway.
2772 $keys = array( 'changeset_uuid', 'customize_changeset_uuid', 'customize_theme', 'theme', 'customize_messenger_channel' );
2773 $input_vars = array_merge(
2774 wp_array_slice_assoc( $_GET, $keys ),
2775 wp_array_slice_assoc( $_POST, $keys )
2779 $changeset_uuid = null;
2780 $messenger_channel = null;
2782 if ( $is_customize_admin_page && isset( $input_vars['changeset_uuid'] ) ) {
2783 $changeset_uuid = sanitize_key( $input_vars['changeset_uuid'] );
2784 } elseif ( ! empty( $input_vars['customize_changeset_uuid'] ) ) {
2785 $changeset_uuid = sanitize_key( $input_vars['customize_changeset_uuid'] );
2788 // Note that theme will be sanitized via WP_Theme.
2789 if ( $is_customize_admin_page && isset( $input_vars['theme'] ) ) {
2790 $theme = $input_vars['theme'];
2791 } elseif ( isset( $input_vars['customize_theme'] ) ) {
2792 $theme = $input_vars['customize_theme'];
2794 if ( isset( $input_vars['customize_messenger_channel'] ) ) {
2795 $messenger_channel = sanitize_key( $input_vars['customize_messenger_channel'] );
2798 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2799 $GLOBALS['wp_customize'] = new WP_Customize_Manager( compact( 'changeset_uuid', 'theme', 'messenger_channel' ) );
2803 * Publish a snapshot's changes.
2805 * @param string $new_status New post status.
2806 * @param string $old_status Old post status.
2807 * @param WP_Post $changeset_post Changeset post object.
2809 function _wp_customize_publish_changeset( $new_status, $old_status, $changeset_post ) {
2810 global $wp_customize, $wpdb;
2812 $is_publishing_changeset = (
2813 'customize_changeset' === $changeset_post->post_type
2815 'publish' === $new_status
2817 'publish' !== $old_status
2819 if ( ! $is_publishing_changeset ) {
2823 if ( empty( $wp_customize ) ) {
2824 require_once ABSPATH . WPINC . '/class-wp-customize-manager.php';
2825 $wp_customize = new WP_Customize_Manager( array( 'changeset_uuid' => $changeset_post->post_name ) );
2828 if ( ! did_action( 'customize_register' ) ) {
2830 * When running from CLI or Cron, the customize_register action will need
2831 * to be triggered in order for core, themes, and plugins to register their
2832 * settings. Normally core will add_action( 'customize_register' ) at
2833 * priority 10 to register the core settings, and if any themes/plugins
2834 * also add_action( 'customize_register' ) at the same priority, they
2835 * will have a $wp_customize with those settings registered since they
2836 * call add_action() afterward, normally. However, when manually doing
2837 * the customize_register action after the setup_theme, then the order
2838 * will be reversed for two actions added at priority 10, resulting in
2839 * the core settings no longer being available as expected to themes/plugins.
2840 * So the following manually calls the method that registers the core
2841 * settings up front before doing the action.
2843 remove_action( 'customize_register', array( $wp_customize, 'register_controls' ) );
2844 $wp_customize->register_controls();
2846 /** This filter is documented in /wp-includes/class-wp-customize-manager.php */
2847 do_action( 'customize_register', $wp_customize );
2849 $wp_customize->_publish_changeset_values( $changeset_post->ID ) ;
2852 * Trash the changeset post if revisions are not enabled. Unpublished
2853 * changesets by default get garbage collected due to the auto-draft status.
2854 * When a changeset post is published, however, it would no longer get cleaned
2855 * out. Ths is a problem when the changeset posts are never displayed anywhere,
2856 * since they would just be endlessly piling up. So here we use the revisions
2857 * feature to indicate whether or not a published changeset should get trashed
2858 * and thus garbage collected.
2860 if ( ! wp_revisions_enabled( $changeset_post ) ) {
2861 $post = $changeset_post;
2862 $post_id = $changeset_post->ID;
2865 * The following re-formulates the logic from wp_trash_post() as done in
2866 * wp_publish_post(). The reason for bypassing wp_trash_post() is that it
2867 * will mutate the the post_content and the post_name when they should be
2870 if ( ! EMPTY_TRASH_DAYS ) {
2871 wp_delete_post( $post_id, true );
2873 /** This action is documented in wp-includes/post.php */
2874 do_action( 'wp_trash_post', $post_id );
2876 add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
2877 add_post_meta( $post_id, '_wp_trash_meta_time', time() );
2879 $old_status = $post->post_status;
2880 $new_status = 'trash';
2881 $wpdb->update( $wpdb->posts, array( 'post_status' => $new_status ), array( 'ID' => $post->ID ) );
2882 clean_post_cache( $post->ID );
2884 $post->post_status = $new_status;
2885 wp_transition_post_status( $new_status, $old_status, $post );
2887 /** This action is documented in wp-includes/post.php */
2888 do_action( 'edit_post', $post->ID, $post );
2890 /** This action is documented in wp-includes/post.php */
2891 do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
2893 /** This action is documented in wp-includes/post.php */
2894 do_action( 'save_post', $post->ID, $post, true );
2896 /** This action is documented in wp-includes/post.php */
2897 do_action( 'wp_insert_post', $post->ID, $post, true );
2899 /** This action is documented in wp-includes/post.php */
2900 do_action( 'trashed_post', $post_id );
2906 * Filters changeset post data upon insert to ensure post_name is intact.
2908 * This is needed to prevent the post_name from being dropped when the post is
2909 * transitioned into pending status by a contributor.
2912 * @see wp_insert_post()
2914 * @param array $post_data An array of slashed post data.
2915 * @param array $supplied_post_data An array of sanitized, but otherwise unmodified post data.
2916 * @returns array Filtered data.
2918 function _wp_customize_changeset_filter_insert_post_data( $post_data, $supplied_post_data ) {
2919 if ( isset( $post_data['post_type'] ) && 'customize_changeset' === $post_data['post_type'] ) {
2921 // Prevent post_name from being dropped, such as when contributor saves a changeset post as pending.
2922 if ( empty( $post_data['post_name'] ) && ! empty( $supplied_post_data['post_name'] ) ) {
2923 $post_data['post_name'] = $supplied_post_data['post_name'];
2930 * Adds settings for the customize-loader script.
2934 function _wp_customize_loader_settings() {
2935 $admin_origin = parse_url( admin_url() );
2936 $home_origin = parse_url( home_url() );
2937 $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
2940 'mobile' => wp_is_mobile(),
2941 'ios' => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
2945 'url' => esc_url( admin_url( 'customize.php' ) ),
2946 'isCrossDomain' => $cross_domain,
2947 'browser' => $browser,
2949 'saveAlert' => __( 'The changes you made will be lost if you navigate away from this page.' ),
2950 'mainIframeTitle' => __( 'Customizer' ),
2954 $script = 'var _wpCustomizeLoaderSettings = ' . wp_json_encode( $settings ) . ';';
2956 $wp_scripts = wp_scripts();
2957 $data = $wp_scripts->get_data( 'customize-loader', 'data' );
2959 $script = "$data\n$script";
2961 $wp_scripts->add_data( 'customize-loader', 'data', $script );
2965 * Returns a URL to load the Customizer.
2969 * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
2970 * The theme's stylesheet will be urlencoded if necessary.
2973 function wp_customize_url( $stylesheet = null ) {
2974 $url = admin_url( 'customize.php' );
2976 $url .= '?theme=' . urlencode( $stylesheet );
2977 return esc_url( $url );
2981 * Prints a script to check whether or not the Customizer is supported,
2982 * and apply either the no-customize-support or customize-support class
2985 * This function MUST be called inside the body tag.
2987 * Ideally, call this function immediately after the body tag is opened.
2988 * This prevents a flash of unstyled content.
2990 * It is also recommended that you add the "no-customize-support" class
2991 * to the body tag by default.
2994 * @since 4.7.0 Support for IE8 and below is explicitly removed via conditional comments.
2996 function wp_customize_support_script() {
2997 $admin_origin = parse_url( admin_url() );
2998 $home_origin = parse_url( home_url() );
2999 $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
3003 <script type="text/javascript">
3004 document.body.className = document.body.className.replace( /(^|\s)(no-)?customize-support(?=\s|$)/, '' ) + ' no-customize-support';
3007 <!--[if gte IE 9]><!-->
3008 <script type="text/javascript">
3010 var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
3012 <?php if ( $cross_domain ) : ?>
3013 request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
3018 b[c] = b[c].replace( rcs, ' ' );
3019 // The customizer requires postMessage and CORS (if the site is cross domain)
3020 b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
3028 * Whether the site is being previewed in the Customizer.
3032 * @global WP_Customize_Manager $wp_customize Customizer instance.
3034 * @return bool True if the site is being previewed in the Customizer, false otherwise.
3036 function is_customize_preview() {
3037 global $wp_customize;
3039 return ( $wp_customize instanceof WP_Customize_Manager ) && $wp_customize->is_preview();