diff --git a/wp-admin/includes/admin-filters.php b/wp-admin/includes/admin-filters.php
index 33354cb073..8f364360fd 100644
--- a/wp-admin/includes/admin-filters.php
+++ b/wp-admin/includes/admin-filters.php
@@ -168,3 +168,10 @@ add_action( 'post_updated', array( 'WP_Privacy_Policy_Content', '_policy_page_up
// Append '(Draft)' to draft page titles in the privacy page dropdown.
add_filter( 'list_pages', '_wp_privacy_settings_filter_draft_page_titles', 10, 2 );
+
+// Attaches filters to enable theme previews in the Site Editor.
+if ( ! empty( $_GET['wp_theme_preview'] ) ) {
+ add_filter( 'stylesheet', 'wp_get_theme_preview_path' );
+ add_filter( 'template', 'wp_get_theme_preview_path' );
+ add_action( 'init', 'wp_attach_theme_preview_middleware' );
+}
diff --git a/wp-admin/includes/admin.php b/wp-admin/includes/admin.php
index ce2ec0c68b..4930e92b71 100644
--- a/wp-admin/includes/admin.php
+++ b/wp-admin/includes/admin.php
@@ -71,6 +71,7 @@ require_once ABSPATH . 'wp-admin/includes/list-table.php';
/** WordPress Theme Administration API */
require_once ABSPATH . 'wp-admin/includes/theme.php';
+require_once ABSPATH . 'wp-admin/includes/theme-previews.php';
/** WordPress Privacy Functions */
require_once ABSPATH . 'wp-admin/includes/privacy-tools.php';
diff --git a/wp-admin/includes/theme-previews.php b/wp-admin/includes/theme-previews.php
new file mode 100644
index 0000000000..55a3679096
--- /dev/null
+++ b/wp-admin/includes/theme-previews.php
@@ -0,0 +1,56 @@
+errors() ) ) {
+ if ( current_filter() === 'template' ) {
+ $theme_path = $wp_theme->get_template();
+ } else {
+ $theme_path = $wp_theme->get_stylesheet();
+ }
+
+ return sanitize_text_field( $theme_path );
+ }
+
+ return $current_stylesheet;
+}
+
+/**
+ * Adds a middleware to `apiFetch` to set the theme for the preview.
+ * 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.
+ *
+ * @since 6.3.0
+ */
+function wp_attach_theme_preview_middleware() {
+ // Don't allow non-admins to preview themes.
+ if ( ! current_user_can( 'switch_themes' ) ) {
+ return;
+ }
+
+ wp_add_inline_script(
+ 'wp-api-fetch',
+ sprintf(
+ 'wp.apiFetch.use( wp.apiFetch.createThemePreviewMiddleware( %s ) );',
+ wp_json_encode( sanitize_text_field( wp_unslash( $_GET['wp_theme_preview'] ) ) )
+ ),
+ 'after'
+ );
+}
diff --git a/wp-admin/includes/theme.php b/wp-admin/includes/theme.php
index 97c554ab73..82b9715631 100644
--- a/wp-admin/includes/theme.php
+++ b/wp-admin/includes/theme.php
@@ -711,16 +711,21 @@ function wp_prepare_themes_for_js( $themes = null ) {
$is_block_theme = $theme->is_block_theme();
if ( $is_block_theme && $can_edit_theme_options ) {
- $customize_action = esc_url( admin_url( 'site-editor.php' ) );
+ $customize_action = admin_url( 'site-editor.php' );
+ if ( $current_theme !== $slug ) {
+ $customize_action = add_query_arg( 'wp_theme_preview', $slug, $customize_action );
+ }
} elseif ( ! $is_block_theme && $can_customize && $can_edit_theme_options ) {
- $customize_action = esc_url(
- add_query_arg(
- array(
- 'return' => urlencode( sanitize_url( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ),
- ),
- wp_customize_url( $slug )
- )
+ $customize_action = wp_customize_url( $slug );
+ }
+ if ( null !== $customize_action ) {
+ $customize_action = add_query_arg(
+ array(
+ 'return' => urlencode( sanitize_url( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) ),
+ ),
+ $customize_action
);
+ $customize_action = esc_url( $customize_action );
}
$update_requires_wp = isset( $updates[ $slug ]['requires'] ) ? $updates[ $slug ]['requires'] : null;
diff --git a/wp-admin/themes.php b/wp-admin/themes.php
index 99534b6276..351fe93dbd 100644
--- a/wp-admin/themes.php
+++ b/wp-admin/themes.php
@@ -555,7 +555,8 @@ foreach ( $themes as $theme ) :
?>
@@ -914,13 +915,11 @@ function wp_theme_auto_update_setting_template() {
$aria_label = sprintf( _x( 'Activate %s', 'theme' ), '{{ data.name }}' );
?>
- <# if ( ! data.blockTheme ) { #>
-
-
- <# } #>
+
+
<# } else { #>