WordPress.org

Make WordPress Core

Ticket #22058: theme.php

File theme.php, 52.7 KB (added by grapplerulrich, 9 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}