diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php
index 4900a622b6c01..12f7afda3b4d5 100644
--- a/lib/compat/wordpress-6.2/rest-api.php
+++ b/lib/compat/wordpress-6.2/rest-api.php
@@ -101,3 +101,18 @@ function gutenberg_register_global_styles_endpoints() {
$editor_settings->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_global_styles_endpoints' );
+
+/**
+ * Updates REST API response for the sidebars and marks them as 'inactive'.
+ *
+ * Note: This can be a part of the `prepare_item_for_response` in `class-wp-rest-sidebars-controller.php`.
+ *
+ * @param WP_REST_Response $response The sidebar response object.
+ * @return WP_REST_Response $response Updated response object.
+ */
+function gutenberg_modify_rest_sidebars_response( $response ) {
+ $response->data['status'] = wp_is_block_theme() ? 'inactive' : 'active';
+
+ return $response;
+}
+add_filter( 'rest_prepare_sidebar', 'gutenberg_modify_rest_sidebars_response' );
diff --git a/lib/compat/wordpress-6.2/theme.php b/lib/compat/wordpress-6.2/theme.php
new file mode 100644
index 0000000000000..79d5520644947
--- /dev/null
+++ b/lib/compat/wordpress-6.2/theme.php
@@ -0,0 +1,23 @@
+is_block_theme() ) {
+ set_theme_mod( 'wp_legacy_sidebars', $wp_registered_sidebars );
+ }
+}
+add_action( 'switch_theme', 'gutenberg_set_legacy_sidebars', 10, 2 );
diff --git a/lib/compat/wordpress-6.2/widgets.php b/lib/compat/wordpress-6.2/widgets.php
new file mode 100644
index 0000000000000..19591ae64607e
--- /dev/null
+++ b/lib/compat/wordpress-6.2/widgets.php
@@ -0,0 +1,32 @@
+ setAttributes( { tagName: value } ) }
/>
+ { ! hasInnerBlocks && (
+
+ ) }
);
}
diff --git a/packages/block-library/src/template-part/edit/import-controls.js b/packages/block-library/src/template-part/edit/import-controls.js
new file mode 100644
index 0000000000000..1512cd936f02c
--- /dev/null
+++ b/packages/block-library/src/template-part/edit/import-controls.js
@@ -0,0 +1,180 @@
+/**
+ * WordPress dependencies
+ */
+import { __, sprintf } from '@wordpress/i18n';
+import { useMemo, useState } from '@wordpress/element';
+import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
+import {
+ Button,
+ FlexBlock,
+ FlexItem,
+ SelectControl,
+ __experimentalHStack as HStack,
+ __experimentalSpacer as Spacer,
+} from '@wordpress/components';
+import {
+ switchToBlockType,
+ getPossibleBlockTransformations,
+} from '@wordpress/blocks';
+import { store as coreStore } from '@wordpress/core-data';
+import { store as noticesStore } from '@wordpress/notices';
+
+/**
+ * Internal dependencies
+ */
+import { useCreateTemplatePartFromBlocks } from './utils/hooks';
+import { transformWidgetToBlock } from './utils/transformers';
+
+export function TemplatePartImportControls( { area, setAttributes } ) {
+ const [ selectedSidebar, setSelectedSidebar ] = useState( '' );
+ const [ isBusy, setIsBusy ] = useState( false );
+
+ const registry = useRegistry();
+ const sidebars = useSelect( ( select ) => {
+ return select( coreStore ).getSidebars( {
+ per_page: -1,
+ _fields: 'id,name,description,status,widgets',
+ } );
+ }, [] );
+ const { createErrorNotice } = useDispatch( noticesStore );
+
+ const createFromBlocks = useCreateTemplatePartFromBlocks(
+ area,
+ setAttributes
+ );
+
+ const options = useMemo( () => {
+ const sidebarOptions = ( sidebars ?? [] )
+ .filter(
+ ( widgetArea ) =>
+ widgetArea.id !== 'wp_inactive_widgets' &&
+ widgetArea.widgets.length > 0
+ )
+ .map( ( widgetArea ) => {
+ return {
+ value: widgetArea.id,
+ label: widgetArea.name,
+ };
+ } );
+
+ if ( ! sidebarOptions.length ) {
+ return [];
+ }
+
+ return [
+ { value: '', label: __( 'Select widget area' ) },
+ ...sidebarOptions,
+ ];
+ }, [ sidebars ] );
+
+ async function createFromWidgets( event ) {
+ event.preventDefault();
+
+ if ( isBusy || ! selectedSidebar ) {
+ return;
+ }
+
+ setIsBusy( true );
+
+ const sidebar = options.find(
+ ( { value } ) => value === selectedSidebar
+ );
+ const { getWidgets } = registry.resolveSelect( coreStore );
+
+ // The widgets API always returns a successful response.
+ const widgets = await getWidgets( {
+ sidebar: sidebar.value,
+ _embed: 'about',
+ } );
+
+ const skippedWidgets = new Set();
+ const blocks = widgets.flatMap( ( widget ) => {
+ const block = transformWidgetToBlock( widget );
+
+ if ( block.name !== 'core/legacy-widget' ) {
+ return block;
+ }
+
+ const transforms = getPossibleBlockTransformations( [
+ block,
+ ] ).filter( ( item ) => {
+ // The block without any transformations can't be a wildcard.
+ if ( ! item.transforms ) {
+ return true;
+ }
+
+ const hasWildCardFrom = item.transforms?.from?.find(
+ ( from ) => from.blocks && from.blocks.includes( '*' )
+ );
+ const hasWildCardTo = item.transforms?.to?.find(
+ ( to ) => to.blocks && to.blocks.includes( '*' )
+ );
+
+ return ! hasWildCardFrom && ! hasWildCardTo;
+ } );
+
+ // Skip the block if we have no matching transformations.
+ if ( ! transforms.length ) {
+ skippedWidgets.add( widget.id_base );
+ return [];
+ }
+
+ // Try transforming the Legacy Widget into a first matching block.
+ return switchToBlockType( block, transforms[ 0 ].name );
+ } );
+
+ await createFromBlocks(
+ blocks,
+ /* translators: %s: name of the widget area */
+ sprintf( __( 'Widget area: %s' ), sidebar.label )
+ );
+
+ if ( skippedWidgets.size ) {
+ createErrorNotice(
+ sprintf(
+ /* translators: %s: the list of widgets */
+ __( 'Unable to import the following widgets: %s.' ),
+ Array.from( skippedWidgets ).join( ', ' )
+ ),
+ {
+ type: 'snackbar',
+ }
+ );
+ }
+
+ setIsBusy( false );
+ }
+
+ return (
+
+
+
+ setSelectedSidebar( value ) }
+ disabled={ ! options.length }
+ __next36pxDefaultSize
+ __nextHasNoMarginBottom
+ />
+
+
+
+
+
+
+ );
+}
diff --git a/packages/block-library/src/template-part/edit/index.js b/packages/block-library/src/template-part/edit/index.js
index 83b1a8d450b6c..502bd3d00feef 100644
--- a/packages/block-library/src/template-part/edit/index.js
+++ b/packages/block-library/src/template-part/edit/index.js
@@ -141,6 +141,7 @@ export default function TemplatePartEdit( {
isEntityAvailable={ isEntityAvailable }
templatePartId={ templatePartId }
defaultWrapper={ areaObject.tagName }
+ hasInnerBlocks={ innerBlocks.length > 0 }
/>
{ isPlaceholder && (
diff --git a/packages/block-library/src/template-part/edit/utils/transformers.js b/packages/block-library/src/template-part/edit/utils/transformers.js
new file mode 100644
index 0000000000000..fdef84d785b90
--- /dev/null
+++ b/packages/block-library/src/template-part/edit/utils/transformers.js
@@ -0,0 +1,37 @@
+/**
+ * WordPress dependencies
+ */
+import { createBlock, parse } from '@wordpress/blocks';
+
+/**
+ * Converts a widget entity record into a block.
+ *
+ * @param {Object} widget The widget entity record.
+ * @return {Object} a block (converted from the entity record).
+ */
+export function transformWidgetToBlock( widget ) {
+ if ( widget.id_base === 'block' ) {
+ const parsedBlocks = parse( widget.instance.raw.content, {
+ __unstableSkipAutop: true,
+ } );
+ if ( ! parsedBlocks.length ) {
+ return createBlock( 'core/paragraph', {}, [] );
+ }
+
+ return parsedBlocks[ 0 ];
+ }
+
+ let attributes;
+ if ( widget._embedded.about[ 0 ].is_multi ) {
+ attributes = {
+ idBase: widget.id_base,
+ instance: widget.instance,
+ };
+ } else {
+ attributes = {
+ id: widget.id,
+ };
+ }
+
+ return createBlock( 'core/legacy-widget', attributes, [] );
+}
diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json
index 3414e958344d2..7967b0d63b1d6 100644
--- a/packages/edit-post/package.json
+++ b/packages/edit-post/package.json
@@ -52,6 +52,7 @@
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
"@wordpress/warning": "file:../warning",
+ "@wordpress/widgets": "file:../widgets",
"classnames": "^2.3.1",
"lodash": "^4.17.21",
"memize": "^1.1.0",
diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js
index 5b7c4e01ead7b..2ee4dcdab1704 100644
--- a/packages/edit-post/src/index.js
+++ b/packages/edit-post/src/index.js
@@ -10,6 +10,7 @@ import { render, unmountComponentAtNode } from '@wordpress/element';
import { dispatch, select } from '@wordpress/data';
import { addFilter } from '@wordpress/hooks';
import { store as preferencesStore } from '@wordpress/preferences';
+import { registerLegacyWidgetBlock } from '@wordpress/widgets';
/**
* Internal dependencies
@@ -115,6 +116,7 @@ export function initializeEditor(
}
registerCoreBlocks();
+ registerLegacyWidgetBlock( { inserter: false } );
if ( process.env.IS_GUTENBERG_PLUGIN ) {
__experimentalRegisterExperimentalCoreBlocks( {
enableFSEBlocks: settings.__unstableEnableFullSiteEditingBlocks,
diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json
index 6f42f00b2c625..a89f52e5dddda 100644
--- a/packages/edit-site/package.json
+++ b/packages/edit-site/package.json
@@ -54,6 +54,7 @@
"@wordpress/style-engine": "file:../style-engine",
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
+ "@wordpress/widgets": "file:../widgets",
"classnames": "^2.3.1",
"colord": "^2.9.2",
"downloadjs": "^1.4.7",
diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js
index 4b619f7aac9a9..d2cbb2f4aa04d 100644
--- a/packages/edit-site/src/index.js
+++ b/packages/edit-site/src/index.js
@@ -16,6 +16,7 @@ import { store as editorStore } from '@wordpress/editor';
import { store as interfaceStore } from '@wordpress/interface';
import { store as preferencesStore } from '@wordpress/preferences';
import { addFilter } from '@wordpress/hooks';
+import { registerLegacyWidgetBlock } from '@wordpress/widgets';
/**
* Internal dependencies
@@ -109,6 +110,7 @@ export function initializeEditor( id, settings ) {
dispatch( blocksStore ).__experimentalReapplyBlockTypeFilters();
registerCoreBlocks();
+ registerLegacyWidgetBlock( { inserter: false } );
if ( process.env.IS_GUTENBERG_PLUGIN ) {
__experimentalRegisterExperimentalCoreBlocks( {
enableFSEBlocks: true,
diff --git a/packages/widgets/src/index.js b/packages/widgets/src/index.js
index 6c88d65556d8b..e55b16ff12b35 100644
--- a/packages/widgets/src/index.js
+++ b/packages/widgets/src/index.js
@@ -18,11 +18,21 @@ export * from './utils';
* Note that for the block to be useful, any scripts required by a widget must
* be loaded into the page.
*
+ * @param {Object} supports Block support settings.
* @see https://developer.wordpress.org/block-editor/how-to-guides/widgets/legacy-widget-block/
*/
-export function registerLegacyWidgetBlock() {
+export function registerLegacyWidgetBlock( supports = {} ) {
const { metadata, settings, name } = legacyWidget;
- registerBlockType( { name, ...metadata }, settings );
+ registerBlockType(
+ { name, ...metadata },
+ {
+ ...settings,
+ supports: {
+ ...settings.supports,
+ ...supports,
+ },
+ }
+ );
}
/**