Make WordPress Core

Ticket #22058: theme.php

File theme.php, 52.7 KB (added by grapplerulrich, 14 years ago)

theme.php

Line 
1<?php
2/**
3 * Theme, template, and stylesheet functions.
4 *
5 * @package WordPress
6 * @subpackage Theme
7 */
8
9/**
10 * Returns an array of WP_Theme objects based on the arguments.
11 *
12 * Despite advances over get_themes(), this function is quite expensive, and grows
13 * linearly with additional themes. Stick to wp_get_theme() if possible.
14 *
15 * @since 3.4.0
16 *
17 * @param array $args The search arguments. Optional.
18 * - errors mixed True to return themes with errors, false to return themes without errors, null
19 * to return all themes. Defaults to false.
20 * - allowed mixed (Multisite) True to return only allowed themes for a site. False to return only
21 * disallowed themes for a site. 'site' to return only site-allowed themes. 'network'
22 * to return only network-allowed themes. Null to return all themes. Defaults to null.
23 * - blog_id int (Multisite) The blog ID used to calculate which themes are allowed. Defaults to 0,
24 * synonymous for the current blog.
25 * @return Array of WP_Theme objects.
26 */
27function wp_get_themes( $args = array() ) {
28 global $wp_theme_directories;
29
30 $defaults = array( 'errors' => false, 'allowed' => null, 'blog_id' => 0 );
31 $args = wp_parse_args( $args, $defaults );
32
33 $theme_directories = search_theme_directories();
34
35 if ( count( $wp_theme_directories ) > 1 ) {
36 // Make sure the current theme wins out, in case search_theme_directories() picks the wrong
37 // one in the case of a conflict. (Normally, last registered theme root wins.)
38 $current_theme = get_stylesheet();
39 if ( isset( $theme_directories[ $current_theme ] ) ) {
40 $root_of_current_theme = get_raw_theme_root( $current_theme );
41 if ( ! in_array( $root_of_current_theme, $wp_theme_directories ) )
42 $root_of_current_theme = WP_CONTENT_DIR . $root_of_current_theme;
43 $theme_directories[ $current_theme ]['theme_root'] = $root_of_current_theme;
44 }
45 }
46
47 if ( empty( $theme_directories ) )
48 return array();
49
50 if ( is_multisite() && null !== $args['allowed'] ) {
51 $allowed = $args['allowed'];
52 if ( 'network' === $allowed )
53 $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_network() );
54 elseif ( 'site' === $allowed )
55 $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed_on_site( $args['blog_id'] ) );
56 elseif ( $allowed )
57 $theme_directories = array_intersect_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
58 else
59 $theme_directories = array_diff_key( $theme_directories, WP_Theme::get_allowed( $args['blog_id'] ) );
60 }
61
62 $themes = array();
63 static $_themes = array();
64
65 foreach ( $theme_directories as $theme => $theme_root ) {
66 if ( isset( $_themes[ $theme_root['theme_root'] . '/' . $theme ] ) )
67 $themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ];
68 else
69 $themes[ $theme ] = $_themes[ $theme_root['theme_root'] . '/' . $theme ] = new WP_Theme( $theme, $theme_root['theme_root'] );
70 }
71
72 if ( null !== $args['errors'] ) {
73 foreach ( $themes as $theme => $wp_theme ) {
74 if ( $wp_theme->errors() != $args['errors'] )
75 unset( $themes[ $theme ] );
76 }
77 }
78
79 return $themes;
80}
81
82/**
83 * Gets a WP_Theme object for a theme.
84 *
85 * @since 3.4.0
86 *
87 * @param string $stylesheet Directory name for the theme. Optional. Defaults to current theme.
88 * @param string $theme_root Absolute path of the theme root to look in. Optional. If not specified, get_raw_theme_root()
89 * is used to calculate the theme root for the $stylesheet provided (or current theme).
90 * @return WP_Theme Theme object. Be sure to check the object's exists() method if you need to confirm the theme's existence.
91 */
92function wp_get_theme( $stylesheet = null, $theme_root = null ) {
93 global $wp_theme_directories;
94
95 if ( empty( $stylesheet ) )
96 $stylesheet = get_stylesheet();
97
98 if ( empty( $theme_root ) ) {
99 $theme_root = get_raw_theme_root( $stylesheet );
100 if ( false === $theme_root )
101 $theme_root = WP_CONTENT_DIR . '/themes';
102 elseif ( ! in_array( $theme_root, (array) $wp_theme_directories ) )
103 $theme_root = WP_CONTENT_DIR . $theme_root;
104 }
105
106 return new WP_Theme( $stylesheet, $theme_root );
107}
108
109/**
110 * Clears the cache held by get_theme_roots() and WP_Theme.
111 *
112 * @since 3.5.0
113 */
114function wp_clean_themes_cache() {
115 delete_site_transient('update_themes');
116 search_theme_directories( true );
117 foreach ( wp_get_themes( array( 'errors' => null ) ) as $theme )
118 $theme->cache_delete();
119}
120
121/**
122 * Whether a child theme is in use.
123 *
124 * @since 3.0.0
125 *
126 * @return bool true if a child theme is in use, false otherwise.
127 **/
128function is_child_theme() {
129 return ( TEMPLATEPATH !== STYLESHEETPATH );
130}
131
132/**
133 * Retrieve name of the current stylesheet.
134 *
135 * The theme name that the administrator has currently set the front end theme
136 * as.
137 *
138 * For all extensive purposes, the template name and the stylesheet name are
139 * going to be the same for most cases.
140 *
141 * @since 1.5.0
142 * @uses apply_filters() Calls 'stylesheet' filter on stylesheet name.
143 *
144 * @return string Stylesheet name.
145 */
146function get_stylesheet() {
147 return apply_filters('stylesheet', get_option('stylesheet'));
148}
149
150/**
151 * Retrieve stylesheet directory path for current theme.
152 *
153 * @since 1.5.0
154 * @uses apply_filters() Calls 'stylesheet_directory' filter on stylesheet directory and theme name.
155 *
156 * @return string Path to current theme directory.
157 */
158function get_stylesheet_directory() {
159 $stylesheet = get_stylesheet();
160 $theme_root = get_theme_root( $stylesheet );
161 $stylesheet_dir = "$theme_root/$stylesheet";
162
163 return apply_filters( 'stylesheet_directory', $stylesheet_dir, $stylesheet, $theme_root );
164}
165
166/**
167 * Retrieve stylesheet directory URI.
168 *
169 * @since 1.5.0
170 *
171 * @return string
172 */
173function get_stylesheet_directory_uri() {
174 $stylesheet = get_stylesheet();
175 $theme_root_uri = get_theme_root_uri( $stylesheet );
176 $stylesheet_dir_uri = "$theme_root_uri/$stylesheet";
177
178 return apply_filters( 'stylesheet_directory_uri', $stylesheet_dir_uri, $stylesheet, $theme_root_uri );
179}
180
181/**
182 * Retrieve URI of current theme stylesheet.
183 *
184 * The stylesheet file name is 'style.css' which is appended to {@link
185 * get_stylesheet_directory_uri() stylesheet directory URI} path.
186 *
187 * @since 1.5.0
188 * @uses apply_filters() Calls 'stylesheet_uri' filter on stylesheet URI path and stylesheet directory URI.
189 *
190 * @return string
191 */
192function get_stylesheet_uri() {
193 $stylesheet_dir_uri = get_stylesheet_directory_uri();
194 $stylesheet_uri = $stylesheet_dir_uri . '/style.css';
195 return apply_filters('stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri);
196}
197
198/**
199 * Retrieve localized stylesheet URI.
200 *
201 * The stylesheet directory for the localized stylesheet files are located, by
202 * default, in the base theme directory. The name of the locale file will be the
203 * locale followed by '.css'. If that does not exist, then the text direction
204 * stylesheet will be checked for existence, for example 'ltr.css'.
205 *
206 * The theme may change the location of the stylesheet directory by either using
207 * the 'stylesheet_directory_uri' filter or the 'locale_stylesheet_uri' filter.
208 * If you want to change the location of the stylesheet files for the entire
209 * WordPress workflow, then change the former. If you just have the locale in a
210 * separate folder, then change the latter.
211 *
212 * @since 2.1.0
213 * @uses apply_filters() Calls 'locale_stylesheet_uri' filter on stylesheet URI path and stylesheet directory URI.
214 *
215 * @return string
216 */
217function get_locale_stylesheet_uri() {
218 global $wp_locale;
219 $stylesheet_dir_uri = get_stylesheet_directory_uri();
220 $dir = get_stylesheet_directory();
221 $locale = get_locale();
222 if ( file_exists("$dir/$locale.css") )
223 $stylesheet_uri = "$stylesheet_dir_uri/$locale.css";
224 elseif ( !empty($wp_locale->text_direction) && file_exists("$dir/{$wp_locale->text_direction}.css") )
225 $stylesheet_uri = "$stylesheet_dir_uri/{$wp_locale->text_direction}.css";
226 else
227 $stylesheet_uri = '';
228 return apply_filters('locale_stylesheet_uri', $stylesheet_uri, $stylesheet_dir_uri);
229}
230
231/**
232 * Retrieve name of the current theme.
233 *
234 * @since 1.5.0
235 * @uses apply_filters() Calls 'template' filter on template option.
236 *
237 * @return string Template name.
238 */
239function get_template() {
240 return apply_filters('template', get_option('template'));
241}
242
243/**
244 * Retrieve current theme directory.
245 *
246 * @since 1.5.0
247 * @uses apply_filters() Calls 'template_directory' filter on template directory path and template name.
248 *
249 * @return string Template directory path.
250 */
251function get_template_directory() {
252 $template = get_template();
253 $theme_root = get_theme_root( $template );
254 $template_dir = "$theme_root/$template";
255
256 return apply_filters( 'template_directory', $template_dir, $template, $theme_root );
257}
258
259/**
260 * Retrieve theme directory URI.
261 *
262 * @since 1.5.0
263 * @uses apply_filters() Calls 'template_directory_uri' filter on template directory URI path and template name.
264 *
265 * @return string Template directory URI.
266 */
267function get_template_directory_uri() {
268 $template = get_template();
269 $theme_root_uri = get_theme_root_uri( $template );
270 $template_dir_uri = "$theme_root_uri/$template";
271
272 return apply_filters( 'template_directory_uri', $template_dir_uri, $template, $theme_root_uri );
273}
274
275/**
276 * Retrieve theme roots.
277 *
278 * @since 2.9.0
279 *
280 * @return array|string An array of theme roots keyed by template/stylesheet or a single theme root if all themes have the same root.
281 */
282function get_theme_roots() {
283 global $wp_theme_directories;
284
285 if ( count($wp_theme_directories) <= 1 )
286 return '/themes';
287
288 $theme_roots = get_site_transient( 'theme_roots' );
289 if ( false === $theme_roots ) {
290 search_theme_directories( true ); // Regenerate the transient.
291 $theme_roots = get_site_transient( 'theme_roots' );
292 }
293 return $theme_roots;
294}
295
296/**
297 * Register a directory that contains themes.
298 *
299 * @since 2.9.0
300 *
301 * @param string $directory Either the full filesystem path to a theme folder or a folder within WP_CONTENT_DIR
302 * @return bool
303 */
304function register_theme_directory( $directory ) {
305 global $wp_theme_directories;
306
307 if ( ! file_exists( $directory ) ) {
308 // Try prepending as the theme directory could be relative to the content directory
309 $directory = WP_CONTENT_DIR . '/' . $directory;
310 // If this directory does not exist, return and do not register
311 if ( ! file_exists( $directory ) )
312 return false;
313 }
314
315 $wp_theme_directories[] = $directory;
316
317 return true;
318}
319
320/**
321 * Search all registered theme directories for complete and valid themes.
322 *
323 * @since 2.9.0
324 *
325 * @param bool $force Optional. Whether to force a new directory scan. Defaults to false.
326 * @return array Valid themes found
327 */
328function search_theme_directories( $force = false ) {
329 global $wp_theme_directories;
330 if ( empty( $wp_theme_directories ) )
331 return false;
332
333 static $found_themes;
334 if ( ! $force && isset( $found_themes ) )
335 return $found_themes;
336
337 $found_themes = array();
338
339 $wp_theme_directories = (array) $wp_theme_directories;
340
341 // Set up maybe-relative, maybe-absolute array of theme directories.
342 // We always want to return absolute, but we need to cache relative
343 // use in for get_theme_root().
344 foreach ( $wp_theme_directories as $theme_root ) {
345 if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) )
346 $relative_theme_roots[ str_replace( WP_CONTENT_DIR, '', $theme_root ) ] = $theme_root;
347 else
348 $relative_theme_roots[ $theme_root ] = $theme_root;
349 }
350
351 if ( $cache_expiration = apply_filters( 'wp_cache_themes_persistently', false, 'search_theme_directories' ) ) {
352 $cached_roots = get_site_transient( 'theme_roots' );
353 if ( is_array( $cached_roots ) ) {
354 foreach ( $cached_roots as $theme_dir => $theme_root ) {
355 // A cached theme root is no longer around, so skip it.
356 if ( ! isset( $relative_theme_roots[ $theme_root ] ) )
357 continue;
358 $found_themes[ $theme_dir ] = array(
359 'theme_file' => $theme_dir . '/style.css',
360 'theme_root' => $relative_theme_roots[ $theme_root ], // Convert relative to absolute.
361 );
362 }
363 return $found_themes;
364 }
365 if ( ! is_int( $cache_expiration ) )
366 $cache_expiration = 1800; // half hour
367 } else {
368 $cache_expiration = 1800; // half hour
369 }
370
371 /* Loop the registered theme directories and extract all themes */
372 foreach ( $wp_theme_directories as $theme_root ) {
373
374 // Start with directories in the root of the current theme directory.
375 $dirs = @ scandir( $theme_root );
376 if ( ! $dirs )
377 return false;
378 foreach ( $dirs as $dir ) {
379 if ( ! is_dir( $theme_root . '/' . $dir ) || $dir[0] == '.' || $dir == 'CVS' )
380 continue;
381 if ( file_exists( $theme_root . '/' . $dir . '/style.css' ) ) {
382 // wp-content/themes/a-single-theme
383 // wp-content/themes is $theme_root, a-single-theme is $dir
384 $found_themes[ $dir ] = array(
385 'theme_file' => $dir . '/style.css',
386 'theme_root' => $theme_root,
387 );
388 } else {
389 $found_theme = false;
390 // wp-content/themes/a-folder-of-themes/*
391 // wp-content/themes is $theme_root, a-folder-of-themes is $dir, then themes are $sub_dirs
392 $sub_dirs = @ scandir( $theme_root . '/' . $dir );
393 if ( ! $sub_dirs )
394 return false;
395 foreach ( $sub_dirs as $sub_dir ) {
396 if ( ! is_dir( $theme_root . '/' . $dir . '/' . $sub_dir ) || $dir[0] == '.' || $dir == 'CVS' )
397 continue;
398 if ( ! file_exists( $theme_root . '/' . $dir . '/' . $sub_dir . '/style.css' ) )
399 continue;
400 $found_themes[ $dir . '/' . $sub_dir ] = array(
401 'theme_file' => $dir . '/' . $sub_dir . '/style.css',
402 'theme_root' => $theme_root,
403 );
404 $found_theme = true;
405 }
406 // Never mind the above, it's just a theme missing a style.css.
407 // Return it; WP_Theme will catch the error.
408 if ( ! $found_theme )
409 $found_themes[ $dir ] = array(
410 'theme_file' => $dir . '/style.css',
411 'theme_root' => $theme_root,
412 );
413 }
414 }
415 }
416
417 asort( $found_themes );
418
419 $theme_roots = array();
420 $relative_theme_roots = array_flip( $relative_theme_roots );
421
422 foreach ( $found_themes as $theme_dir => $theme_data ) {
423 $theme_roots[ $theme_dir ] = $relative_theme_roots[ $theme_data['theme_root'] ]; // Convert absolute to relative.
424 }
425
426 if ( $theme_roots != get_site_transient( 'theme_roots' ) )
427 set_site_transient( 'theme_roots', $theme_roots, $cache_expiration );
428
429 return $found_themes;
430}
431
432/**
433 * Retrieve path to themes directory.
434 *
435 * Does not have trailing slash.
436 *
437 * @since 1.5.0
438 * @uses apply_filters() Calls 'theme_root' filter on path.
439 *
440 * @param string $stylesheet_or_template The stylesheet or template name of the theme
441 * @return string Theme path.
442 */
443function get_theme_root( $stylesheet_or_template = false ) {
444 global $wp_theme_directories;
445
446 if ( $stylesheet_or_template && $theme_root = get_raw_theme_root( $stylesheet_or_template ) ) {
447 // Always prepend WP_CONTENT_DIR unless the root currently registered as a theme directory.
448 // This gives relative theme roots the benefit of the doubt when things go haywire.
449 if ( ! in_array( $theme_root, (array) $wp_theme_directories ) )
450 $theme_root = WP_CONTENT_DIR . $theme_root;
451 } else {
452 $theme_root = WP_CONTENT_DIR . '/themes';
453 }
454
455 return apply_filters( 'theme_root', $theme_root );
456}
457
458/**
459 * Retrieve URI for themes directory.
460 *
461 * Does not have trailing slash.
462 *
463 * @since 1.5.0
464 *
465 * @param string $stylesheet_or_template Optional. The stylesheet or template name of the theme.
466 * Default is to leverage the main theme root.
467 * @param string $theme_root Optional. The theme root for which calculations will be based, preventing
468 * the need for a get_raw_theme_root() call.
469 * @return string Themes URI.
470 */
471function get_theme_root_uri( $stylesheet_or_template = false, $theme_root = false ) {
472 global $wp_theme_directories;
473
474 if ( $stylesheet_or_template && ! $theme_root )
475 $theme_root = get_raw_theme_root( $stylesheet_or_template );
476
477 if ( $stylesheet_or_template && $theme_root ) {
478 if ( in_array( $theme_root, (array) $wp_theme_directories ) ) {
479 // Absolute path. Make an educated guess. YMMV -- but note the filter below.
480 if ( 0 === strpos( $theme_root, WP_CONTENT_DIR ) )
481 $theme_root_uri = content_url( str_replace( WP_CONTENT_DIR, '', $theme_root ) );
482 elseif ( 0 === strpos( $theme_root, ABSPATH ) )
483 $theme_root_uri = site_url( str_replace( ABSPATH, '', $theme_root ) );
484 elseif ( 0 === strpos( $theme_root, WP_PLUGIN_DIR ) || 0 === strpos( $theme_root, WPMU_PLUGIN_DIR ) )
485 $theme_root_uri = plugins_url( basename( $theme_root ), $theme_root );
486 else
487 $theme_root_uri = $theme_root;
488 } else {
489 $theme_root_uri = content_url( $theme_root );
490 }
491 } else {
492 $theme_root_uri = content_url( 'themes' );
493 }
494
495 return apply_filters( 'theme_root_uri', $theme_root_uri, get_option('siteurl'), $stylesheet_or_template );
496}
497
498/**
499 * Get the raw theme root relative to the content directory with no filters applied.
500 *
501 * @since 3.1.0
502 *
503 * @param string $stylesheet_or_template The stylesheet or template name of the theme
504 * @param bool $skip_cache Optional. Whether to skip the cache. Defaults to false, meaning the cache is used.
505 * @return string Theme root
506 */
507function get_raw_theme_root( $stylesheet_or_template, $skip_cache = false ) {
508 global $wp_theme_directories;
509
510 if ( count($wp_theme_directories) <= 1 )
511 return '/themes';
512
513 $theme_root = false;
514
515 // If requesting the root for the current theme, consult options to avoid calling get_theme_roots()
516 if ( ! $skip_cache ) {
517 if ( get_option('stylesheet') == $stylesheet_or_template )
518 $theme_root = get_option('stylesheet_root');
519 elseif ( get_option('template') == $stylesheet_or_template )
520 $theme_root = get_option('template_root');
521 }
522
523 if ( empty($theme_root) ) {
524 $theme_roots = get_theme_roots();
525 if ( !empty($theme_roots[$stylesheet_or_template]) )
526 $theme_root = $theme_roots[$stylesheet_or_template];
527 }
528
529 return $theme_root;
530}
531
532/**
533 * Display localized stylesheet link element.
534 *
535 * @since 2.1.0
536 */
537function locale_stylesheet() {
538 $stylesheet = get_locale_stylesheet_uri();
539 if ( empty($stylesheet) )
540 return;
541 echo '<link rel="stylesheet" href="' . $stylesheet . '" type="text/css" media="screen" />';
542}
543
544/**
545 * Start preview theme output buffer.
546 *
547 * Will only preform task if the user has permissions and template and preview
548 * query variables exist.
549 *
550 * @since 2.6.0
551 */
552function preview_theme() {
553 if ( ! (isset($_GET['template']) && isset($_GET['preview'])) )
554 return;
555
556 if ( !current_user_can( 'switch_themes' ) )
557 return;
558
559 // Admin Thickbox requests
560 if ( isset( $_GET['preview_iframe'] ) )
561 show_admin_bar( false );
562
563 $_GET['template'] = preg_replace('|[^a-z0-9_./-]|i', '', $_GET['template']);
564
565 if ( validate_file($_GET['template']) )
566 return;
567
568 add_filter( 'template', '_preview_theme_template_filter' );
569
570 if ( isset($_GET['stylesheet']) ) {
571 $_GET['stylesheet'] = preg_replace('|[^a-z0-9_./-]|i', '', $_GET['stylesheet']);
572 if ( validate_file($_GET['stylesheet']) )
573 return;
574 add_filter( 'stylesheet', '_preview_theme_stylesheet_filter' );
575 }
576
577 // Prevent theme mods to current theme being used on theme being previewed
578 add_filter( 'pre_option_theme_mods_' . get_option( 'stylesheet' ), '__return_empty_array' );
579
580 ob_start( 'preview_theme_ob_filter' );
581}
582add_action('setup_theme', 'preview_theme');
583
584/**
585 * Private function to modify the current template when previewing a theme
586 *
587 * @since 2.9.0
588 * @access private
589 *
590 * @return string
591 */
592function _preview_theme_template_filter() {
593 return isset($_GET['template']) ? $_GET['template'] : '';
594}
595
596/**
597 * Private function to modify the current stylesheet when previewing a theme
598 *
599 * @since 2.9.0
600 * @access private
601 *
602 * @return string
603 */
604function _preview_theme_stylesheet_filter() {
605 return isset($_GET['stylesheet']) ? $_GET['stylesheet'] : '';
606}
607
608/**
609 * Callback function for ob_start() to capture all links in the theme.
610 *
611 * @since 2.6.0
612 * @access private
613 *
614 * @param string $content
615 * @return string
616 */
617function preview_theme_ob_filter( $content ) {
618 return preg_replace_callback( "|(<a.*?href=([\"']))(.*?)([\"'].*?>)|", 'preview_theme_ob_filter_callback', $content );
619}
620
621/**
622 * Manipulates preview theme links in order to control and maintain location.
623 *
624 * Callback function for preg_replace_callback() to accept and filter matches.
625 *
626 * @since 2.6.0
627 * @access private
628 *
629 * @param array $matches
630 * @return string
631 */
632function preview_theme_ob_filter_callback( $matches ) {
633 if ( strpos($matches[4], 'onclick') !== false )
634 $matches[4] = preg_replace('#onclick=([\'"]).*?(?<!\\\)\\1#i', '', $matches[4]); //Strip out any onclicks from rest of <a>. (?<!\\\) means to ignore the '" if its escaped by \ to prevent breaking mid-attribute.
635 if (
636 ( false !== strpos($matches[3], '/wp-admin/') )
637 ||
638 ( false !== strpos( $matches[3], '://' ) && 0 !== strpos( $matches[3], home_url() ) )
639 ||
640 ( false !== strpos($matches[3], '/feed/') )
641 ||
642 ( false !== strpos($matches[3], '/trackback/') )
643 )
644 return $matches[1] . "#$matches[2] onclick=$matches[2]return false;" . $matches[4];
645
646 $link = add_query_arg( array( 'preview' => 1, 'template' => $_GET['template'], 'stylesheet' => @$_GET['stylesheet'], 'preview_iframe' => 1 ), $matches[3] );
647 if ( 0 === strpos($link, 'preview=1') )
648 $link = "?$link";
649 return $matches[1] . esc_attr( $link ) . $matches[4];
650}
651
652/**
653 * Switches the theme.
654 *
655 * Accepts one argument: $stylesheet of the theme. It also accepts an additional function signature
656 * of two arguments: $template then $stylesheet. This is for backwards compatibility.
657 *
658 * @since 2.5.0
659 * @uses do_action() Calls 'switch_theme' action, passing the new theme.
660 *
661 * @param string $stylesheet Stylesheet name
662 */
663function switch_theme( $stylesheet ) {
664 global $wp_theme_directories, $sidebars_widgets;
665
666 if ( is_array( $sidebars_widgets ) )
667 set_theme_mod( 'sidebars_widgets', array( 'time' => time(), 'data' => $sidebars_widgets ) );
668
669 $old_theme = wp_get_theme();
670 $new_theme = wp_get_theme( $stylesheet );
671
672 if ( func_num_args() > 1 ) {
673 $template = $stylesheet;
674 $stylesheet = func_get_arg( 1 );
675 } else {
676 $template = $new_theme->get_template();
677 }
678
679 update_option( 'template', $template );
680 update_option( 'stylesheet', $stylesheet );
681
682 if ( count( $wp_theme_directories ) > 1 ) {
683 update_option( 'template_root', get_raw_theme_root( $template, true ) );
684 update_option( 'stylesheet_root', get_raw_theme_root( $stylesheet, true ) );
685 }
686
687 $new_name = $new_theme->get('Name');
688
689 update_option( 'current_theme', $new_name );
690
691 if ( is_admin() && false === get_option( 'theme_mods_' . $stylesheet ) ) {
692 $default_theme_mods = (array) get_option( 'mods_' . $new_name );
693 add_option( "theme_mods_$stylesheet", $default_theme_mods );
694 }
695
696 update_option( 'theme_switched', $old_theme->get_stylesheet() );
697 do_action( 'switch_theme', $new_name, $new_theme );
698}
699
700/**
701 * Checks that current theme files 'index.php' and 'style.css' exists.
702 *
703 * Does not check the default theme, which is the fallback and should always exist.
704 * Will switch theme to the fallback theme if current theme does not validate.
705 * You can use the 'validate_current_theme' filter to return false to
706 * disable this functionality.
707 *
708 * @since 1.5.0
709 * @see WP_DEFAULT_THEME
710 *
711 * @return bool
712 */
713function validate_current_theme() {
714 // Don't validate during an install/upgrade.
715 if ( defined('WP_INSTALLING') || !apply_filters( 'validate_current_theme', true ) )
716 return true;
717
718 if ( get_template() != WP_DEFAULT_THEME && !file_exists(get_template_directory() . '/index.php') ) {
719 switch_theme( WP_DEFAULT_THEME );
720 return false;
721 }
722
723 if ( get_stylesheet() != WP_DEFAULT_THEME && !file_exists(get_template_directory() . '/style.css') ) {
724 switch_theme( WP_DEFAULT_THEME );
725 return false;
726 }
727
728 if ( is_child_theme() && ! file_exists( get_stylesheet_directory() . '/style.css' ) ) {
729 switch_theme( WP_DEFAULT_THEME );
730 return false;
731 }
732
733 return true;
734}
735
736/**
737 * Retrieve all theme modifications.
738 *
739 * @since 3.1.0
740 *
741 * @return array Theme modifications.
742 */
743function get_theme_mods() {
744 $theme_slug = get_option( 'stylesheet' );
745 if ( false === ( $mods = get_option( "theme_mods_$theme_slug" ) ) ) {
746 $theme_name = get_option( 'current_theme' );
747 if ( false === $theme_name )
748 $theme_name = wp_get_theme()->get('Name');
749 $mods = get_option( "mods_$theme_name" ); // Deprecated location.
750 if ( is_admin() && false !== $mods ) {
751 update_option( "theme_mods_$theme_slug", $mods );
752 delete_option( "mods_$theme_name" );
753 }
754 }
755 return $mods;
756}
757
758/**
759 * Retrieve theme modification value for the current theme.
760 *
761 * If the modification name does not exist, then the $default will be passed
762 * through {@link http://php.net/sprintf sprintf()} PHP function with the first
763 * string the template directory URI and the second string the stylesheet
764 * directory URI.
765 *
766 * @since 2.1.0
767 * @uses apply_filters() Calls 'theme_mod_$name' filter on the value.
768 *
769 * @param string $name Theme modification name.
770 * @param bool|string $default
771 * @return string
772 */
773function get_theme_mod( $name, $default = false ) {
774 $mods = get_theme_mods();
775
776 if ( isset( $mods[ $name ] ) )
777 return apply_filters( "theme_mod_$name", $mods[ $name ] );
778
779 if ( is_string( $default ) )
780 $default = sprintf( $default, get_template_directory_uri(), get_stylesheet_directory_uri() );
781
782 return apply_filters( "theme_mod_$name", $default );
783}
784
785/**
786 * Update theme modification value for the current theme.
787 *
788 * @since 2.1.0
789 *
790 * @param string $name Theme modification name.
791 * @param string $value theme modification value.
792 */
793function set_theme_mod( $name, $value ) {
794 $mods = get_theme_mods();
795
796 $mods[ $name ] = $value;
797
798 $theme = get_option( 'stylesheet' );
799 update_option( "theme_mods_$theme", $mods );
800}
801
802/**
803 * Remove theme modification name from current theme list.
804 *
805 * If removing the name also removes all elements, then the entire option will
806 * be removed.
807 *
808 * @since 2.1.0
809 *
810 * @param string $name Theme modification name.
811 * @return null
812 */
813function remove_theme_mod( $name ) {
814 $mods = get_theme_mods();
815
816 if ( ! isset( $mods[ $name ] ) )
817 return;
818
819 unset( $mods[ $name ] );
820
821 if ( empty( $mods ) )
822 return remove_theme_mods();
823
824 $theme = get_option( 'stylesheet' );
825 update_option( "theme_mods_$theme", $mods );
826}
827
828/**
829 * Remove theme modifications option for current theme.
830 *
831 * @since 2.1.0
832 */
833function remove_theme_mods() {
834 delete_option( 'theme_mods_' . get_option( 'stylesheet' ) );
835
836 // Old style.
837 $theme_name = get_option( 'current_theme' );
838 if ( false === $theme_name )
839 $theme_name = wp_get_theme()->get('Name');
840 delete_option( 'mods_' . $theme_name );
841}
842
843/**
844 * Retrieve text color for custom header.
845 *
846 * @since 2.1.0
847 *
848 * @return string
849 */
850function get_header_textcolor() {
851 return get_theme_mod('header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
852}
853
854/**
855 * Display text color for custom header.
856 *
857 * @since 2.1.0
858 */
859function header_textcolor() {
860 echo get_header_textcolor();
861}
862
863/**
864 * Whether to display the header text.
865 *
866 * @since 3.4.0
867 *
868 * @return bool
869 */
870function display_header_text() {
871 if ( ! current_theme_supports( 'custom-header', 'header-text' ) )
872 return false;
873
874 $text_color = get_theme_mod( 'header_textcolor', get_theme_support( 'custom-header', 'default-text-color' ) );
875 return 'blank' != $text_color;
876}
877
878/**
879 * Retrieve header image for custom header.
880 *
881 * @since 2.1.0
882 *
883 * @return string
884 */
885function get_header_image() {
886 $url = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
887
888 if ( 'remove-header' == $url )
889 return false;
890
891 if ( is_random_header_image() )
892 $url = get_random_header_image();
893
894 return esc_url_raw( set_url_scheme( $url ) );
895}
896
897/**
898 * Get random header image data from registered images in theme.
899 *
900 * @since 3.4.0
901 *
902 * @access private
903 *
904 * @return string Path to header image
905 */
906
907function _get_random_header_data() {
908 static $_wp_random_header;
909
910 if ( empty( $_wp_random_header ) ) {
911 global $_wp_default_headers;
912 $header_image_mod = get_theme_mod( 'header_image', '' );
913 $headers = array();
914
915 if ( 'random-uploaded-image' == $header_image_mod )
916 $headers = get_uploaded_header_images();
917 elseif ( ! empty( $_wp_default_headers ) ) {
918 if ( 'random-default-image' == $header_image_mod ) {
919 $headers = $_wp_default_headers;
920 } else {
921 if ( current_theme_supports( 'custom-header', 'random-default' ) )
922 $headers = $_wp_default_headers;
923 }
924 }
925
926 if ( empty( $headers ) )
927 return new stdClass;
928
929 $_wp_random_header = (object) $headers[ array_rand( $headers ) ];
930
931 $_wp_random_header->url = sprintf( $_wp_random_header->url, get_template_directory_uri(), get_stylesheet_directory_uri() );
932 $_wp_random_header->thumbnail_url = sprintf( $_wp_random_header->thumbnail_url, get_template_directory_uri(), get_stylesheet_directory_uri() );
933 }
934 return $_wp_random_header;
935}
936
937/**
938 * Get random header image url from registered images in theme.
939 *
940 * @since 3.2.0
941 *
942 * @return string Path to header image
943 */
944
945function get_random_header_image() {
946 $random_image = _get_random_header_data();
947 if ( empty( $random_image->url ) )
948 return '';
949 return $random_image->url;
950}
951
952/**
953 * Check if random header image is in use.
954 *
955 * Always true if user expressly chooses the option in Appearance > Header.
956 * Also true if theme has multiple header images registered, no specific header image
957 * is chosen, and theme turns on random headers with add_theme_support().
958 *
959 * @since 3.2.0
960 *
961 * @param string $type The random pool to use. any|default|uploaded
962 * @return boolean
963 */
964function is_random_header_image( $type = 'any' ) {
965 $header_image_mod = get_theme_mod( 'header_image', get_theme_support( 'custom-header', 'default-image' ) );
966
967 if ( 'any' == $type ) {
968 if ( 'random-default-image' == $header_image_mod || 'random-uploaded-image' == $header_image_mod || ( '' != get_random_header_image() && empty( $header_image_mod ) ) )
969 return true;
970 } else {
971 if ( "random-$type-image" == $header_image_mod )
972 return true;
973 elseif ( 'default' == $type && empty( $header_image_mod ) && '' != get_random_header_image() )
974 return true;
975 }
976
977 return false;
978}
979
980/**
981 * Display header image path.
982 *
983 * @since 2.1.0
984 */
985function header_image() {
986 echo get_header_image();
987}
988
989/**
990 * Get the header images uploaded for the current theme.
991 *
992 * @since 3.2.0
993 *
994 * @return array
995 */
996function get_uploaded_header_images() {
997 $header_images = array();
998
999 // @todo caching
1000 $headers = get_posts( array( 'post_type' => 'attachment', 'meta_key' => '_wp_attachment_is_custom_header', 'meta_value' => get_option('stylesheet'), 'orderby' => 'none', 'nopaging' => true ) );
1001
1002 if ( empty( $headers ) )
1003 return array();
1004
1005 foreach ( (array) $headers as $header ) {
1006 $url = esc_url_raw( $header->guid );
1007 $header_data = wp_get_attachment_metadata( $header->ID );
1008 $header_index = basename($url);
1009 $header_images[$header_index] = array();
1010 $header_images[$header_index]['attachment_id'] = $header->ID;
1011 $header_images[$header_index]['url'] = $url;
1012 $header_images[$header_index]['thumbnail_url'] = $url;
1013 $header_images[$header_index]['width'] = $header_data['width'];
1014 $header_images[$header_index]['height'] = $header_data['height'];
1015 }
1016
1017 return $header_images;
1018}
1019
1020/**
1021 * Get the header image data.
1022 *
1023 * @since 3.4.0
1024 *
1025 * @return object
1026 */
1027function get_custom_header() {
1028 $data = is_random_header_image()? _get_random_header_data() : get_theme_mod( 'header_image_data' );
1029 $default = array(
1030 'url' => '',
1031 'thumbnail_url' => '',
1032 'width' => get_theme_support( 'custom-header', 'width' ),
1033 'height' => get_theme_support( 'custom-header', 'height' ),
1034 );
1035 return (object) wp_parse_args( $data, $default );
1036}
1037
1038/**
1039 * Register a selection of default headers to be displayed by the custom header admin UI.
1040 *
1041 * @since 3.0.0
1042 *
1043 * @param array $headers Array of headers keyed by a string id. The ids point to arrays containing 'url', 'thumbnail_url', and 'description' keys.
1044 */
1045function register_default_headers( $headers ) {
1046 global $_wp_default_headers;
1047
1048 $_wp_default_headers = array_merge( (array) $_wp_default_headers, (array) $headers );
1049}
1050
1051/**
1052 * Unregister default headers.
1053 *
1054 * This function must be called after register_default_headers() has already added the
1055 * header you want to remove.
1056 *
1057 * @see register_default_headers()
1058 * @since 3.0.0
1059 *
1060 * @param string|array $header The header string id (key of array) to remove, or an array thereof.
1061 * @return True on success, false on failure.
1062 */
1063function unregister_default_headers( $header ) {
1064 global $_wp_default_headers;
1065 if ( is_array( $header ) ) {
1066 array_map( 'unregister_default_headers', $header );
1067 } elseif ( isset( $_wp_default_headers[ $header ] ) ) {
1068 unset( $_wp_default_headers[ $header ] );
1069 return true;
1070 } else {
1071 return false;
1072 }
1073}
1074
1075/**
1076 * Retrieve background image for custom background.
1077 *
1078 * @since 3.0.0
1079 *
1080 * @return string
1081 */
1082function get_background_image() {
1083 return get_theme_mod('background_image', get_theme_support( 'custom-background', 'default-image' ) );
1084}
1085
1086/**
1087 * Display background image path.
1088 *
1089 * @since 3.0.0
1090 */
1091function background_image() {
1092 echo get_background_image();
1093}
1094
1095/**
1096 * Retrieve value for custom background color.
1097 *
1098 * @since 3.0.0
1099 *
1100 * @return string
1101 */
1102function get_background_color() {
1103 return get_theme_mod('background_color', get_theme_support( 'custom-background', 'default-color' ) );
1104}
1105
1106/**
1107 * Display background color value.
1108 *
1109 * @since 3.0.0
1110 */
1111function background_color() {
1112 echo get_background_color();
1113}
1114
1115/**
1116 * Default custom background callback.
1117 *
1118 * @since 3.0.0
1119 * @access protected
1120 */
1121function _custom_background_cb() {
1122 // $background is the saved custom image, or the default image.
1123 $background = set_url_scheme( get_background_image() );
1124
1125 // $color is the saved custom color.
1126 // A default has to be specified in style.css. It will not be printed here.
1127 $color = get_theme_mod( 'background_color' );
1128
1129 if ( ! $background && ! $color )
1130 return;
1131
1132 $style = $color ? "background-color: #$color;" : '';
1133
1134 if ( $background ) {
1135 $image = " background-image: url('$background');";
1136
1137 $repeat = get_theme_mod( 'background_repeat', 'repeat' );
1138 if ( ! in_array( $repeat, array( 'no-repeat', 'repeat-x', 'repeat-y', 'repeat' ) ) )
1139 $repeat = 'repeat';
1140 $repeat = " background-repeat: $repeat;";
1141
1142 $hposition = get_theme_mod( 'background_position_x', 'left' );
1143 if ( ! in_array( $hposition, array( 'center', 'right', 'left' ) ) )
1144 $hposition = 'left';
1145 $vposition = get_theme_mod( 'background_position_y', 'top' );
1146 if ( ! in_array( $vposition, array( 'center', 'top', 'bottom' ) ) )
1147 $vposition = 'top';
1148 $position = " background-position: $vposition $hposition;";
1149
1150 $attachment = get_theme_mod( 'background_attachment', 'scroll' );
1151 if ( ! in_array( $attachment, array( 'fixed', 'scroll' ) ) )
1152 $attachment = 'scroll';
1153 $attachment = " background-attachment: $attachment;";
1154
1155 $style .= $image . $repeat . $position . $attachment;
1156 }
1157?>
1158<style type="text/css" id="custom-background-css">
1159body.custom-background { <?php echo trim( $style ); ?> }
1160</style>
1161<?php
1162}
1163
1164/**
1165 * Add callback for custom TinyMCE editor stylesheets.
1166 *
1167 * The parameter $stylesheet is the name of the stylesheet, relative to
1168 * the theme root. It also accepts an array of stylesheets.
1169 * It is optional and defaults to 'editor-style.css'.
1170 *
1171 * This function automatically adds another stylesheet with -rtl prefix, e.g. editor-style-rtl.css.
1172 * If that file doesn't exist, it is removed before adding the stylesheet(s) to TinyMCE.
1173 * If an array of stylesheets is passed to add_editor_style(),
1174 * RTL is only added for the first stylesheet.
1175 *
1176 * Since version 3.4 the TinyMCE body has .rtl CSS class.
1177 * It is a better option to use that class and add any RTL styles to the main stylesheet.
1178 *
1179 * @since 3.0.0
1180 *
1181 * @param mixed $stylesheet Optional. Stylesheet name or array thereof, relative to theme root.
1182 * Defaults to 'editor-style.css'
1183 */
1184function add_editor_style( $stylesheet = 'editor-style.css' ) {
1185
1186 add_theme_support( 'editor-style' );
1187
1188 if ( ! is_admin() )
1189 return;
1190
1191 global $editor_styles;
1192 $editor_styles = (array) $editor_styles;
1193 $stylesheet = (array) $stylesheet;
1194 if ( is_rtl() ) {
1195 $rtl_stylesheet = str_replace('.css', '-rtl.css', $stylesheet[0]);
1196 $stylesheet[] = $rtl_stylesheet;
1197 }
1198
1199 $editor_styles = array_merge( $editor_styles, $stylesheet );
1200}
1201
1202/**
1203 * Removes all visual editor stylesheets.
1204 *
1205 * @since 3.1.0
1206 *
1207 * @return bool True on success, false if there were no stylesheets to remove.
1208 */
1209function remove_editor_styles() {
1210 if ( ! current_theme_supports( 'editor-style' ) )
1211 return false;
1212 _remove_theme_support( 'editor-style' );
1213 if ( is_admin() )
1214 $GLOBALS['editor_styles'] = array();
1215 return true;
1216}
1217
1218/**
1219 * Allows a theme to register its support of a certain feature
1220 *
1221 * Must be called in the theme's functions.php file to work.
1222 * If attached to a hook, it must be after_setup_theme.
1223 * The init hook may be too late for some features.
1224 *
1225 * @since 2.9.0
1226 * @param string $feature the feature being added
1227 */
1228function add_theme_support( $feature ) {
1229 global $_wp_theme_features;
1230
1231 if ( func_num_args() == 1 )
1232 $args = true;
1233 else
1234 $args = array_slice( func_get_args(), 1 );
1235
1236 switch ( $feature ) {
1237 case 'post-formats' :
1238 if ( is_array( $args[0] ) )
1239 $args[0] = array_intersect( $args[0], array_keys( get_post_format_slugs() ) );
1240 break;
1241
1242 case 'custom-header-uploads' :
1243 return add_theme_support( 'custom-header', array( 'uploads' => true ) );
1244 break;
1245
1246 case 'custom-header' :
1247 if ( ! is_array( $args ) )
1248 $args = array( 0 => array() );
1249
1250 $defaults = array(
1251 'default-image' => '',
1252 'random-default' => false,
1253 'width' => 0,
1254 'height' => 0,
1255 'flex-height' => false,
1256 'flex-width' => false,
1257 'default-text-color' => '',
1258 'header-text' => true,
1259 'uploads' => true,
1260 'wp-head-callback' => '',
1261 'admin-head-callback' => '',
1262 'admin-preview-callback' => '',
1263 );
1264
1265 $jit = isset( $args[0]['__jit'] );
1266 unset( $args[0]['__jit'] );
1267
1268 // Merge in data from previous add_theme_support() calls.
1269 // The first value registered wins. (A child theme is set up first.)
1270 if ( isset( $_wp_theme_features['custom-header'] ) )
1271 $args[0] = wp_parse_args( $_wp_theme_features['custom-header'][0], $args[0] );
1272
1273 // Load in the defaults at the end, as we need to insure first one wins.
1274 // This will cause all constants to be defined, as each arg will then be set to the default.
1275 if ( $jit )
1276 $args[0] = wp_parse_args( $args[0], $defaults );
1277
1278 // If a constant was defined, use that value. Otherwise, define the constant to ensure
1279 // the constant is always accurate (and is not defined later, overriding our value).
1280 // As stated above, the first value wins.
1281 // Once we get to wp_loaded (just-in-time), define any constants we haven't already.
1282 // Constants are lame. Don't reference them. This is just for backwards compatibility.
1283
1284 if ( defined( 'NO_HEADER_TEXT' ) )
1285 $args[0]['header-text'] = ! NO_HEADER_TEXT;
1286 elseif ( isset( $args[0]['header-text'] ) )
1287 define( 'NO_HEADER_TEXT', empty( $args[0]['header-text'] ) );
1288
1289 if ( defined( 'HEADER_IMAGE_WIDTH' ) )
1290 $args[0]['width'] = (int) HEADER_IMAGE_WIDTH;
1291 elseif ( isset( $args[0]['width'] ) )
1292 define( 'HEADER_IMAGE_WIDTH', (int) $args[0]['width'] );
1293
1294 if ( defined( 'HEADER_IMAGE_HEIGHT' ) )
1295 $args[0]['height'] = (int) HEADER_IMAGE_HEIGHT;
1296 elseif ( isset( $args[0]['height'] ) )
1297 define( 'HEADER_IMAGE_HEIGHT', (int) $args[0]['height'] );
1298
1299 if ( defined( 'HEADER_TEXTCOLOR' ) )
1300 $args[0]['default-text-color'] = HEADER_TEXTCOLOR;
1301 elseif ( isset( $args[0]['default-text-color'] ) )
1302 define( 'HEADER_TEXTCOLOR', $args[0]['default-text-color'] );
1303
1304 if ( defined( 'HEADER_IMAGE' ) )
1305 $args[0]['default-image'] = HEADER_IMAGE;
1306 elseif ( isset( $args[0]['default-image'] ) )
1307 define( 'HEADER_IMAGE', $args[0]['default-image'] );
1308
1309 if ( $jit && ! empty( $args[0]['default-image'] ) )
1310 $args[0]['random-default'] = false;
1311
1312 // If headers are supported, and we still don't have a defined width or height,
1313 // we have implicit flex sizes.
1314 if ( $jit ) {
1315 if ( empty( $args[0]['width'] ) && empty( $args[0]['flex-width'] ) )
1316 $args[0]['flex-width'] = true;
1317 if ( empty( $args[0]['height'] ) && empty( $args[0]['flex-height'] ) )
1318 $args[0]['flex-height'] = true;
1319 }
1320
1321 break;
1322
1323 case 'custom-background' :
1324 if ( ! is_array( $args ) )
1325 $args = array( 0 => array() );
1326
1327 $defaults = array(
1328 'default-image' => '',
1329 'default-color' => '',
1330 'wp-head-callback' => '_custom_background_cb',
1331 'admin-head-callback' => '',
1332 'admin-preview-callback' => '',
1333 );
1334
1335 $jit = isset( $args[0]['__jit'] );
1336 unset( $args[0]['__jit'] );
1337
1338 // Merge in data from previous add_theme_support() calls. The first value registered wins.
1339 if ( isset( $_wp_theme_features['custom-background'] ) )
1340 $args[0] = wp_parse_args( $_wp_theme_features['custom-background'][0], $args[0] );
1341
1342 if ( $jit )
1343 $args[0] = wp_parse_args( $args[0], $defaults );
1344
1345 if ( defined( 'BACKGROUND_COLOR' ) )
1346 $args[0]['default-color'] = BACKGROUND_COLOR;
1347 elseif ( isset( $args[0]['default-color'] ) || $jit )
1348 define( 'BACKGROUND_COLOR', $args[0]['default-color'] );
1349
1350 if ( defined( 'BACKGROUND_IMAGE' ) )
1351 $args[0]['default-image'] = BACKGROUND_IMAGE;
1352 elseif ( isset( $args[0]['default-image'] ) || $jit )
1353 define( 'BACKGROUND_IMAGE', $args[0]['default-image'] );
1354
1355 break;
1356 }
1357
1358 $_wp_theme_features[ $feature ] = $args;
1359}
1360
1361/**
1362 * Registers the internal custom header and background routines.
1363 *
1364 * @since 3.4.0
1365 * @access private
1366 */
1367function _custom_header_background_just_in_time() {
1368 global $custom_image_header, $custom_background;
1369
1370 if ( current_theme_supports( 'custom-header' ) ) {
1371 // In case any constants were defined after an add_custom_image_header() call, re-run.
1372 add_theme_support( 'custom-header', array( '__jit' => true ) );
1373
1374 $args = get_theme_support( 'custom-header' );
1375 if ( $args[0]['wp-head-callback'] )
1376 add_action( 'wp_head', $args[0]['wp-head-callback'] );
1377
1378 if ( is_admin() ) {
1379 require_once( ABSPATH . 'wp-admin/custom-header.php' );
1380 $custom_image_header = new Custom_Image_Header( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
1381 }
1382 }
1383
1384 if ( current_theme_supports( 'custom-background' ) ) {
1385 // In case any constants were defined after an add_custom_background() call, re-run.
1386 add_theme_support( 'custom-background', array( '__jit' => true ) );
1387
1388 $args = get_theme_support( 'custom-background' );
1389 add_action( 'wp_head', $args[0]['wp-head-callback'] );
1390
1391 if ( is_admin() ) {
1392 require_once( ABSPATH . 'wp-admin/custom-background.php' );
1393 $custom_background = new Custom_Background( $args[0]['admin-head-callback'], $args[0]['admin-preview-callback'] );
1394 }
1395 }
1396}
1397add_action( 'wp_loaded', '_custom_header_background_just_in_time' );
1398
1399/**
1400 * Gets the theme support arguments passed when registering that support
1401 *
1402 * @since 3.1
1403 * @param string $feature the feature to check
1404 * @return array The array of extra arguments
1405 */
1406function get_theme_support( $feature ) {
1407 global $_wp_theme_features;
1408 if ( ! isset( $_wp_theme_features[ $feature ] ) )
1409 return false;
1410
1411 if ( func_num_args() <= 1 )
1412 return $_wp_theme_features[ $feature ];
1413
1414 $args = array_slice( func_get_args(), 1 );
1415 switch ( $feature ) {
1416 case 'custom-header' :
1417 case 'custom-background' :
1418 if ( isset( $_wp_theme_features[ $feature ][0][ $args[0] ] ) )
1419 return $_wp_theme_features[ $feature ][0][ $args[0] ];
1420 return false;
1421 break;
1422 default :
1423 return $_wp_theme_features[ $feature ];
1424 break;
1425 }
1426}
1427
1428/**
1429 * Allows a theme to de-register its support of a certain feature
1430 *
1431 * Should be called in the theme's functions.php file. Generally would
1432 * be used for child themes to override support from the parent theme.
1433 *
1434 * @since 3.0.0
1435 * @see add_theme_support()
1436 * @param string $feature the feature being added
1437 * @return bool Whether feature was removed.
1438 */
1439function remove_theme_support( $feature ) {
1440 // Blacklist: for internal registrations not used directly by themes.
1441 if ( in_array( $feature, array( 'editor-style', 'widgets', 'menus' ) ) )
1442 return false;
1443
1444 return _remove_theme_support( $feature );
1445}
1446
1447/**
1448 * Do not use. Removes theme support internally, ignorant of the blacklist.
1449 *
1450 * @access private
1451 * @since 3.1.0
1452 */
1453function _remove_theme_support( $feature ) {
1454 global $_wp_theme_features;
1455
1456 switch ( $feature ) {
1457 case 'custom-header-uploads' :
1458 if ( ! isset( $_wp_theme_features['custom-header'] ) )
1459 return false;
1460 add_theme_support( 'custom-header', array( 'uploads' => false ) );
1461 return; // Do not continue - custom-header-uploads no longer exists.
1462 }
1463
1464 if ( ! isset( $_wp_theme_features[ $feature ] ) )
1465 return false;
1466
1467 switch ( $feature ) {
1468 case 'custom-header' :
1469 if ( false === did_action( 'wp_loaded', '_custom_header_background_just_in_time' ) )
1470 break;
1471 $support = get_theme_support( 'custom-header' );
1472 if ( $support[0]['wp-head-callback'] )
1473 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
1474 remove_action( 'admin_menu', array( $GLOBALS['custom_image_header'], 'init' ) );
1475 unset( $GLOBALS['custom_image_header'] );
1476 break;
1477
1478 case 'custom-background' :
1479 if ( false === did_action( 'wp_loaded', '_custom_header_background_just_in_time' ) )
1480 break;
1481 $support = get_theme_support( 'custom-background' );
1482 remove_action( 'wp_head', $support[0]['wp-head-callback'] );
1483 remove_action( 'admin_menu', array( $GLOBALS['custom_background'], 'init' ) );
1484 unset( $GLOBALS['custom_background'] );
1485 break;
1486 }
1487
1488 unset( $_wp_theme_features[ $feature ] );
1489 return true;
1490}
1491
1492/**
1493 * Checks a theme's support for a given feature
1494 *
1495 * @since 2.9.0
1496 * @param string $feature the feature being checked
1497 * @return boolean
1498 */
1499function current_theme_supports( $feature ) {
1500 global $_wp_theme_features;
1501
1502 if ( 'custom-header-uploads' == $feature )
1503 return current_theme_supports( 'custom-header', 'uploads' );
1504
1505 if ( !isset( $_wp_theme_features[$feature] ) )
1506 return false;
1507
1508 // If no args passed then no extra checks need be performed
1509 if ( func_num_args() <= 1 )
1510 return true;
1511
1512 $args = array_slice( func_get_args(), 1 );
1513
1514 switch ( $feature ) {
1515 case 'post-thumbnails':
1516 // post-thumbnails can be registered for only certain content/post types by passing
1517 // an array of types to add_theme_support(). If no array was passed, then
1518 // any type is accepted
1519 if ( true === $_wp_theme_features[$feature] ) // Registered for all types
1520 return true;
1521 $content_type = $args[0];
1522 return in_array( $content_type, $_wp_theme_features[$feature][0] );
1523 break;
1524
1525 case 'post-formats':
1526 // specific post formats can be registered by passing an array of types to
1527 // add_theme_support()
1528 $post_format = $args[0];
1529 return in_array( $post_format, $_wp_theme_features[$feature][0] );
1530 break;
1531
1532 case 'custom-header':
1533 case 'custom-background' :
1534 // specific custom header and background capabilities can be registered by passing
1535 // an array to add_theme_support()
1536 $header_support = $args[0];
1537 return ( isset( $_wp_theme_features[$feature][0][$header_support] ) && $_wp_theme_features[$feature][0][$header_support] );
1538 break;
1539 }
1540
1541 return apply_filters('current_theme_supports-' . $feature, true, $args, $_wp_theme_features[$feature]);
1542}
1543
1544/**
1545 * Checks a theme's support for a given feature before loading the functions which implement it.
1546 *
1547 * @since 2.9.0
1548 * @param string $feature the feature being checked
1549 * @param string $include the file containing the functions that implement the feature
1550 */
1551function require_if_theme_supports( $feature, $include) {
1552 if ( current_theme_supports( $feature ) )
1553 require ( $include );
1554}
1555
1556/**
1557 * Checks an attachment being deleted to see if it's a header or background image.
1558 *
1559 * If true it removes the theme modification which would be pointing at the deleted
1560 * attachment
1561 *
1562 * @access private
1563 * @since 3.0.0
1564 * @param int $id the attachment id
1565 */
1566function _delete_attachment_theme_mod( $id ) {
1567 $attachment_image = wp_get_attachment_url( $id );
1568 $header_image = get_header_image();
1569 $background_image = get_background_image();
1570
1571 if ( $header_image && $header_image == $attachment_image )
1572 remove_theme_mod( 'header_image' );
1573
1574 if ( $background_image && $background_image == $attachment_image )
1575 remove_theme_mod( 'background_image' );
1576}
1577
1578add_action( 'delete_attachment', '_delete_attachment_theme_mod' );
1579
1580/**
1581 * Checks if a theme has been changed and runs 'after_switch_theme' hook on the next WP load
1582 *
1583 * @since 3.3.0
1584 */
1585function check_theme_switched() {
1586 if ( $stylesheet = get_option( 'theme_switched' ) ) {
1587 $old_theme = wp_get_theme( $stylesheet );
1588
1589 if ( $old_theme->exists() )
1590 do_action( 'after_switch_theme', $old_theme->get('Name'), $old_theme );
1591 else
1592 do_action( 'after_switch_theme', $stylesheet );
1593
1594 update_option( 'theme_switched', false );
1595 }
1596}
1597
1598/**
1599 * Includes and instantiates the WP_Customize_Manager class.
1600 *
1601 * Fires when ?wp_customize=on or on wp-admin/customize.php.
1602 *
1603 * @since 3.4.0
1604 */
1605function _wp_customize_include() {
1606 if ( ! ( ( isset( $_REQUEST['wp_customize'] ) && 'on' == $_REQUEST['wp_customize'] )
1607 || ( is_admin() && 'customize.php' == basename( $_SERVER['PHP_SELF'] ) )
1608 ) )
1609 return;
1610
1611 require( ABSPATH . WPINC . '/class-wp-customize-manager.php' );
1612 // Init Customize class
1613 $GLOBALS['wp_customize'] = new WP_Customize_Manager;
1614}
1615add_action( 'plugins_loaded', '_wp_customize_include' );
1616
1617/**
1618 * Adds settings for the customize-loader script.
1619 *
1620 * @since 3.4.0
1621 */
1622function _wp_customize_loader_settings() {
1623 global $wp_scripts;
1624
1625 $admin_origin = parse_url( admin_url() );
1626 $home_origin = parse_url( home_url() );
1627 $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
1628
1629 $browser = array(
1630 'mobile' => wp_is_mobile(),
1631 'ios' => wp_is_mobile() && preg_match( '/iPad|iPod|iPhone/', $_SERVER['HTTP_USER_AGENT'] ),
1632 );
1633
1634 $settings = array(
1635 'url' => esc_url( admin_url( 'customize.php' ) ),
1636 'isCrossDomain' => $cross_domain,
1637 'browser' => $browser,
1638 );
1639
1640 $script = 'var _wpCustomizeLoaderSettings = ' . json_encode( $settings ) . ';';
1641
1642 $data = $wp_scripts->get_data( 'customize-loader', 'data' );
1643 if ( $data )
1644 $script = "$data\n$script";
1645
1646 $wp_scripts->add_data( 'customize-loader', 'data', $script );
1647}
1648add_action( 'admin_enqueue_scripts', '_wp_customize_loader_settings' );
1649
1650/**
1651 * Returns a URL to load the theme customizer.
1652 *
1653 * @since 3.4.0
1654 *
1655 * @param string $stylesheet Optional. Theme to customize. Defaults to current theme.
1656 * The theme's stylesheet will be urlencoded if necessary.
1657 */
1658function wp_customize_url( $stylesheet = null ) {
1659 $url = admin_url( 'customize.php' );
1660 if ( $stylesheet )
1661 $url .= '?theme=' . urlencode( $stylesheet );
1662 return esc_url( $url );
1663}
1664
1665/**
1666 * Prints a script to check whether or not the customizer is supported,
1667 * and apply either the no-customize-support or customize-support class
1668 * to the body.
1669 *
1670 * This function MUST be called inside the body tag.
1671 *
1672 * Ideally, call this function immediately after the body tag is opened.
1673 * This prevents a flash of unstyled content.
1674 *
1675 * It is also recommended that you add the "no-customize-support" class
1676 * to the body tag by default.
1677 *
1678 * @since 3.4.0
1679 */
1680function wp_customize_support_script() {
1681 $admin_origin = parse_url( admin_url() );
1682 $home_origin = parse_url( home_url() );
1683 $cross_domain = ( strtolower( $admin_origin[ 'host' ] ) != strtolower( $home_origin[ 'host' ] ) );
1684
1685 ?>
1686 <script type="text/javascript">
1687 (function() {
1688 var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');
1689
1690<?php if ( $cross_domain ): ?>
1691 request = (function(){ var xhr = new XMLHttpRequest(); return ('withCredentials' in xhr); })();
1692<?php else: ?>
1693 request = true;
1694<?php endif; ?>
1695
1696 b[c] = b[c].replace( rcs, ' ' );
1697 b[c] += ( window.postMessage && request ? ' ' : ' no-' ) + cs;
1698 }());
1699 </script>
1700 <?php
1701}