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, + }, + } + ); } /**