1<?php
2/**
3 * Theme previews using the Site Editor for block themes.
4 *
5 * @package WordPress
6 */
7
8/**
9 * Filters the blog option to return the path for the previewed theme.
10 *
11 * @since 6.3.0
12 *
13 * @param string $current_stylesheet The current theme's stylesheet or template path.
14 * @return string The previewed theme's stylesheet or template path.
15 */
16function wp_get_theme_preview_path( $current_stylesheet = null ) {
17 if ( ! current_user_can( 'switch_themes' ) ) {
18 return $current_stylesheet;
19 }
20
21 $preview_stylesheet = ! empty( $_GET['wp_theme_preview'] ) ? sanitize_text_field( wp_unslash( $_GET['wp_theme_preview'] ) ) : null;
22 $wp_theme = wp_get_theme( $preview_stylesheet );
23 if ( ! is_wp_error( $wp_theme->errors() ) ) {
24 if ( current_filter() === 'template' ) {
25 $theme_path = $wp_theme->get_template();
26 } else {
27 $theme_path = $wp_theme->get_stylesheet();
28 }
29
30 return sanitize_text_field( $theme_path );
31 }
32
33 return $current_stylesheet;
34}
35
36/**
37 * Adds a middleware to `apiFetch` to set the theme for the preview.
38 * This adds a `wp_theme_preview` URL parameter to API requests from the Site Editor, so they also respond as if the theme is set to the value of the parameter.
39 *
40 * @since 6.3.0
41 */
42function wp_attach_theme_preview_middleware() {
43 // Don't allow non-admins to preview themes.
44 if ( ! current_user_can( 'switch_themes' ) ) {
45 return;
46 }
47
48 wp_add_inline_script(
49 'wp-api-fetch',
50 sprintf(
51 'wp.apiFetch.use( wp.apiFetch.createThemePreviewMiddleware( %s ) );',
52 wp_json_encode( sanitize_text_field( wp_unslash( $_GET['wp_theme_preview'] ) ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
53 ),
54 'after'
55 );
56}
57
58/**
59 * Set a JavaScript constant for theme activation.
60 *
61 * Sets the JavaScript global WP_BLOCK_THEME_ACTIVATE_NONCE containing the nonce
62 * required to activate a theme. For use within the site editor.
63 *
64 * @see https://github.com/WordPress/gutenberg/pull/41836
65 *
66 * @since 6.3.0
67 * @access private
68 */
69function wp_block_theme_activate_nonce() {
70 $nonce_handle = 'switch-theme_' . wp_get_theme_preview_path();
71 ?>
72 <script type="text/javascript">
73 window.WP_BLOCK_THEME_ACTIVATE_NONCE = <?php echo wp_json_encode( wp_create_nonce( $nonce_handle ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); ?>;
74 </script>
75 <?php
76}
77
78/**
79 * Add filters and actions to enable Block Theme Previews in the Site Editor.
80 *
81 * The filters and actions should be added after `pluggable.php` is included as they may
82 * trigger code that uses `current_user_can()` which requires functionality from `pluggable.php`.
83 *
84 * @since 6.3.2
85 */
86function wp_initialize_theme_preview_hooks() {
87 if ( ! empty( $_GET['wp_theme_preview'] ) ) {
88 add_filter( 'stylesheet', 'wp_get_theme_preview_path' );
89 add_filter( 'template', 'wp_get_theme_preview_path' );
90 add_action( 'init', 'wp_attach_theme_preview_middleware' );
91 add_action( 'admin_head', 'wp_block_theme_activate_nonce' );
92 }
93}
94