From 649d66619aec7698e72ac6e6de3653ba7aadd203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 30 Jul 2020 16:59:24 +0200 Subject: [PATCH 01/41] First stab at sidebars_widgets based widget management --- gutenberg.php | 7 + lib/class-wp-widget-block.php | 92 +++++++++ lib/load.php | 3 + lib/widgets.php | 2 - .../src/widget-area/edit/inner-blocks.js | 2 +- packages/core-data/src/entities.js | 8 + .../index.js | 20 +- packages/edit-widgets/src/index.js | 1 + packages/edit-widgets/src/store/actions.js | 0 packages/edit-widgets/src/store/controls.js | 177 ++++++++++++++++++ packages/edit-widgets/src/store/index.js | 37 ++++ packages/edit-widgets/src/store/reducer.js | 6 + packages/edit-widgets/src/store/resolvers.js | 90 +++++++++ packages/edit-widgets/src/store/selectors.js | 46 +++++ packages/edit-widgets/src/store/utils.js | 30 +++ 15 files changed, 507 insertions(+), 14 deletions(-) create mode 100644 lib/class-wp-widget-block.php create mode 100644 packages/edit-widgets/src/store/actions.js create mode 100644 packages/edit-widgets/src/store/controls.js create mode 100644 packages/edit-widgets/src/store/index.js create mode 100644 packages/edit-widgets/src/store/reducer.js create mode 100644 packages/edit-widgets/src/store/resolvers.js create mode 100644 packages/edit-widgets/src/store/selectors.js create mode 100644 packages/edit-widgets/src/store/utils.js diff --git a/gutenberg.php b/gutenberg.php index 55579611942c9..2a4f5355295ef 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -181,3 +181,10 @@ function register_site_icon_url( $response ) { } add_filter( 'rest_index', 'register_site_icon_url' ); + + +function gutenberg_register_widgets() { + register_widget( 'WP_Widget_Block' ); +} + +add_action( 'widgets_init', 'gutenberg_register_widgets' ); diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php new file mode 100644 index 0000000000000..4dd3eb226b657 --- /dev/null +++ b/lib/class-wp-widget-block.php @@ -0,0 +1,92 @@ + '', + 'content' => '', + ); + + /** + * Sets up a new Block widget instance. + * + * @since 4.8.1 + */ + public function __construct() { + $widget_ops = array( + 'classname' => 'widget_block', + 'description' => __( 'Gutenberg block.' ), + 'customize_selective_refresh' => true, + ); + $control_ops = array( + 'width' => 400, + 'height' => 350, + ); + parent::__construct( 'block', __( 'Gutenberg Block' ), $widget_ops, $control_ops ); + } + + /** + * Outputs the content for the current Block widget instance. + * + * @since 4.8.1 + * + * @global WP_Post $post Global post object. + * + * @param array $args Display arguments including 'before_title', 'after_title', + * 'before_widget', and 'after_widget'. + * @param array $instance Settings for the current Block widget instance. + */ + public function widget( $args, $instance ) { + echo do_blocks( $instance[ 'content' ] ); + } + + /** + * Handles updating settings for the current Block widget instance. + * + * @since 4.8.1 + * + * @param array $new_instance New settings for this instance as input by the user via + * WP_Widget::form(). + * @param array $old_instance Old settings for this instance. + * @return array Settings to save or bool false to cancel saving. + */ + public function update( $new_instance, $old_instance ) { + $instance = array_merge( $this->default_instance, $old_instance ); + $instance['title'] = sanitize_text_field( $new_instance['title'] ); + $instance['content'] = $new_instance['content']; + return $instance; + } + + /** + * Outputs the Block widget settings form. + * + * @see WP_Widget_Custom_HTML::render_control_template_scripts() + * + * @param array $instance Current instance. + */ + public function form( $instance ) { + $instance = wp_parse_args( (array) $instance, $this->default_instance ); + echo do_blocks( $instance[ 'content' ] ); + ?> + + + { - const { canUser, getEntityRecords } = select( 'core' ); + const { sidebarsPosts, hasUploadPermissions } = useSelect( ( select ) => { + const { getSidebarsPosts } = select( 'core/edit-widgets' ); return { - areas: getEntityRecords( 'root', 'widgetArea' ) || EMPTY_ARRAY, + sidebarsPosts: getSidebarsPosts() || EMPTY_ARRAY, hasUploadPermissions: defaultTo( - canUser( 'create', 'media' ), + select( 'core' ).canUser( 'create', 'media' ), true ), }; } ); + const [ blocks, setBlocks ] = useState( [] ); useEffect( () => { - if ( ! areas || ! areas.length || blocks.length > 0 ) { + if ( ! sidebarsPosts || ! sidebarsPosts.length || blocks.length > 0 ) { return; } setBlocks( - areas.map( ( { id, name } ) => { - return createBlock( 'core/widget-area', { - id, - name, - } ); + sidebarsPosts.map( ( post ) => { + return post.blocks[ 0 ]; } ) ); - }, [ areas, blocks ] ); + }, [ sidebarsPosts, blocks ] ); const settings = useMemo( () => { let mediaUploadBlockEditor; diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index ef10dd0f50960..4e5adccb4db2a 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -11,6 +11,7 @@ import { /** * Internal dependencies */ +import './store'; import './hooks'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; import CustomizerEditWidgetsInitializer from './components/customizer-edit-widgets-initializer'; diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/edit-widgets/src/store/controls.js b/packages/edit-widgets/src/store/controls.js new file mode 100644 index 0000000000000..639350e900c89 --- /dev/null +++ b/packages/edit-widgets/src/store/controls.js @@ -0,0 +1,177 @@ +/** + * WordPress dependencies + */ +import { default as triggerApiFetch } from '@wordpress/api-fetch'; +import { createRegistryControl } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { buildSidebarsPostsQuery } from './utils'; + +/** + * Trigger an API Fetch request. + * + * @param {Object} request API Fetch Request Object. + * @return {Object} control descriptor. + */ +export function apiFetch( request ) { + return { + type: 'API_FETCH', + request, + }; +} + +/** + * Returns a list of pending actions for given post id. + * + * @param {number} postId Post ID. + * @return {Array} List of pending actions. + */ +export function getPendingActions( postId ) { + return { + type: 'GET_PENDING_ACTIONS', + postId, + }; +} + +/** + * Returns boolean indicating whether or not an action processing specified + * post is currently running. + * + * @param {number} postId Post ID. + * @return {Object} Action. + */ +export function isProcessingPost( postId ) { + return { + type: 'IS_PROCESSING_POST', + postId, + }; +} + +/** + * Selects menuItemId -> clientId mapping (necessary for saving the navigation). + * + * @param {number} postId Navigation post ID. + * @return {Object} Action. + */ +export function getMenuItemToClientIdMapping( postId ) { + return { + type: 'GET_MENU_ITEM_TO_CLIENT_ID_MAPPING', + postId, + }; +} + +/** + * Resolves navigation post for given menuId. + * + * @see selectors.js + * @param {number} menuId Menu ID. + * @return {Object} Action. + */ +export function getNavigationPostForMenu( menuId ) { + return { + type: 'SELECT', + registryName: 'core/edit-navigation', + selectorName: 'getNavigationPostForMenu', + args: [ menuId ], + }; +} + +/** + * Resolves menu items for given menu id. + * + * @param {number} menuId Menu ID. + * @param query + * @return {Object} Action. + */ +export function resolveSidebars() { + return { + type: 'RESOLVE_SIDEBARS', + query: {}, + }; +} + +/** + * Calls a selector using chosen registry. + * + * @param {string} registryName Registry name. + * @param {string} selectorName Selector name. + * @param {Array} args Selector arguments. + * @return {Object} control descriptor. + */ +export function select( registryName, selectorName, ...args ) { + return { + type: 'SELECT', + registryName, + selectorName, + args, + }; +} + +/** + * Dispatches an action using chosen registry. + * + * @param {string} registryName Registry name. + * @param {string} actionName Action name. + * @param {Array} args Selector arguments. + * @return {Object} control descriptor. + */ +export function dispatch( registryName, actionName, ...args ) { + return { + type: 'DISPATCH', + registryName, + actionName, + args, + }; +} + +const controls = { + API_FETCH( { request } ) { + return triggerApiFetch( request ); + }, + + SELECT: createRegistryControl( + ( registry ) => ( { registryName, selectorName, args } ) => { + return registry.select( registryName )[ selectorName ]( ...args ); + } + ), + + GET_PENDING_ACTIONS: createRegistryControl( + ( registry ) => ( { postId } ) => { + return ( + getState( registry ).processingQueue[ postId ] + ?.pendingActions || [] + ); + } + ), + + IS_PROCESSING_POST: createRegistryControl( + ( registry ) => ( { postId } ) => { + return getState( registry ).processingQueue[ postId ]?.inProgress; + } + ), + + GET_MENU_ITEM_TO_CLIENT_ID_MAPPING: createRegistryControl( + ( registry ) => ( { postId } ) => { + return getState( registry ).mapping[ postId ] || {}; + } + ), + + DISPATCH: createRegistryControl( + ( registry ) => ( { registryName, actionName, args } ) => { + return registry.dispatch( registryName )[ actionName ]( ...args ); + } + ), + + RESOLVE_SIDEBARS: createRegistryControl( ( registry ) => ( { query } ) => { + return registry + .__experimentalResolveSelect( 'core' ) + .getSidebars( query ); + } ), +}; + +const getState = ( registry ) => + registry.stores[ 'core/edit-navigation' ].store.getState(); + +export default controls; diff --git a/packages/edit-widgets/src/store/index.js b/packages/edit-widgets/src/store/index.js new file mode 100644 index 0000000000000..fdd2cdc915fb2 --- /dev/null +++ b/packages/edit-widgets/src/store/index.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { registerStore } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import reducer from './reducer'; +import * as resolvers from './resolvers'; +import * as selectors from './selectors'; +import * as actions from './actions'; +import controls from './controls'; + +/** + * Module Constants + */ +const MODULE_KEY = 'core/edit-widgets'; + +/** + * Block editor data store configuration. + * + * @see https://github.com/WordPress/gutenberg/blob/master/packages/data/README.md#registerStore + * + * @type {Object} + */ +export const storeConfig = { + reducer, + controls, + selectors, + resolvers, + actions, +}; + +const store = registerStore( MODULE_KEY, storeConfig ); + +export default store; diff --git a/packages/edit-widgets/src/store/reducer.js b/packages/edit-widgets/src/store/reducer.js new file mode 100644 index 0000000000000..841b6140ba2ed --- /dev/null +++ b/packages/edit-widgets/src/store/reducer.js @@ -0,0 +1,6 @@ +/** + * WordPress dependencies + */ +import { combineReducers } from '@wordpress/data'; + +export default combineReducers( {} ); diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js new file mode 100644 index 0000000000000..b61085f175753 --- /dev/null +++ b/packages/edit-widgets/src/store/resolvers.js @@ -0,0 +1,90 @@ +/** + * WordPress dependencies + */ +import { parse, createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { resolveSidebars, select, dispatch } from './controls'; +import { + KIND, + POST_TYPE, + buildSidebarsPostsQuery, + buildSidebarPostId, +} from './utils'; + +export function* getSidebarsPosts() { + const postsQuery = buildSidebarsPostsQuery(); + + // Dispatch startResolution to skip the execution of the real getEntityRecord resolver - it would + // issue an http request and fail. + const args = [ KIND, POST_TYPE, postsQuery ]; + yield dispatch( 'core', 'startResolution', 'getEntityRecords', args ); + + yield resolveSidebars(); + const sidebars = yield select( 'core', 'getSidebars' ); + const posts = sidebars.map( ( sidebar ) => { + const widgetAreaBlock = createWidgetAreaBlock( sidebar ); + return createStubPost( sidebar.id, widgetAreaBlock ); + } ); + + yield persistPosts( posts, postsQuery ); + + // Dispatch finishResolution to conclude startResolution dispatched earlier + yield dispatch( 'core', 'finishResolution', 'getEntityRecords', args ); +} + +const createStubPost = ( sidebarId, widgetAreaBlock ) => { + const id = buildSidebarPostId( sidebarId ); + return { + id, + slug: id, + status: 'draft', + type: 'sidebar-page', + blocks: [ widgetAreaBlock ], + meta: { + sidebarId, + }, + }; +}; + +const persistPosts = ( posts, query ) => + dispatch( 'core', 'receiveEntityRecords', KIND, POST_TYPE, posts, query ); + +/** + * Converts a list of widgets into a widget-area block. + * + * @param {Array} sidebar a list of widgets + * @return {Object} Navigation block + */ +function createWidgetAreaBlock( sidebar ) { + return createBlock( + 'core/widget-area', + { + id: sidebar.id, + name: sidebar.name, + }, + sidebar.widgets.flatMap( convertWidgetToBlock ) + ); +} + +function convertWidgetToBlock( widget ) { + // @TODO: filter based on something that's less likely to change or be translated + if ( widget.name === 'Gutenberg Block' ) { + const parsedBlocks = parse( widget.settings.content ); + if ( ! parsedBlocks.length ) { + // @TODO: an empty block + return createBlock( 'core/paragraph', {}, [] ); + } + } + + // @TODO: reuse the rendered string we got from the API + return createBlock( + 'core/legacy-widget', + { + id: widget.id, + }, + [] + ); +} diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js new file mode 100644 index 0000000000000..3275c169d88f7 --- /dev/null +++ b/packages/edit-widgets/src/store/selectors.js @@ -0,0 +1,46 @@ +/** + * WordPress dependencies + */ +import { createRegistrySelector } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { KIND, POST_TYPE, buildSidebarsPostsQuery } from './utils'; + +/** + * Returns a "stub" sidebar post reflecting the contents of a sidebar with id=sidebarId. The + * post is meant as a convenient to only exists in runtime and should never be saved. It + * enables a convenient way of editing the navigation by using a regular post editor. + * + * @param {number} menuId The id sidebar menu to create a post from. + * @return {null|Object} Post once the resolver fetches it, otherwise null + */ +export const getSidebarsPosts = createRegistrySelector( + ( select ) => ( state ) => { + // When the record is unavailable, calling getEditedEntityRecord triggers a http + // request via it's related resolver. Let's return nothing until getNavigationPostForMenu + // resolver marks the record as resolved. + const postsQuery = buildSidebarsPostsQuery(); + if ( ! hasResolvedSidebarsPost( state, postsQuery ) ) { + return null; + } + return select( 'core' ).getEntityRecords( KIND, POST_TYPE, postsQuery ); + } +); + +/** + * Returns true if the navigation post related to menuId was already resolved. + * + * @param {number} menuId The id of menu. + * @return {boolean} True if the navigation post related to menuId was already resolved, false otherwise. + */ +export const hasResolvedSidebarsPost = createRegistrySelector( + ( select ) => ( state, query ) => { + return select( 'core' ).hasFinishedResolution( 'getEntityRecords', [ + KIND, + POST_TYPE, + query, + ] ); + } +); diff --git a/packages/edit-widgets/src/store/utils.js b/packages/edit-widgets/src/store/utils.js new file mode 100644 index 0000000000000..85726cdb069a6 --- /dev/null +++ b/packages/edit-widgets/src/store/utils.js @@ -0,0 +1,30 @@ +/** + * "Kind" of the navigation post. + * + * @type {string} + */ +export const KIND = 'root'; + +/** + * "post type" of the navigation post. + * + * @type {string} + */ +export const POST_TYPE = 'postType'; + +/** + * Builds an ID for a new navigation post. + * + * @param {number} menuId Menu id. + * @return {string} An ID. + */ +export const buildSidebarPostId = ( menuId ) => `sidebar-post-${ menuId }`; + +/** + * Builds a query to resolve sidebars. + * + * @return {Object} Query. + */ +export function buildSidebarsPostsQuery() { + return { type: 'sidebar-page' }; +} From b1ca015c82ce21e378ef867fa9430f18fc6d9622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 31 Jul 2020 12:10:02 +0200 Subject: [PATCH 02/41] Render widgets screen based on sidebars endpoint --- .../src/legacy-widget/edit/index.js | 1 + .../src/widget-area/edit/inner-blocks.js | 2 +- packages/core-data/src/entities.js | 20 ++-- .../sync-customizer.js | 44 +++++---- .../src/components/save-button/index.js | 22 +++-- .../index.js | 1 + .../index.js | 17 ++-- packages/edit-widgets/src/index.js | 1 + packages/edit-widgets/src/store/controls.js | 8 +- packages/edit-widgets/src/store/resolvers.js | 92 +++++++------------ packages/edit-widgets/src/store/selectors.js | 48 ++++++---- packages/edit-widgets/src/store/utils.js | 14 +-- 12 files changed, 128 insertions(+), 142 deletions(-) diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index 12194929461ae..1e19b34907372 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -45,6 +45,7 @@ class LegacyWidgetEdit extends Component { const widgetObject = ( id && availableLegacyWidgets[ id ] ) || ( widgetClass && availableLegacyWidgets[ widgetClass ] ); + if ( ! id && ! widgetClass ) { return ( { // Get widget areas from the store in an `id => blocks` mapping. const getWidgetAreasObject = () => { - const { getEntityRecords, getEditedEntityRecord } = window.wp.data.select( - 'core' - ); - - return getEntityRecords( 'root', 'widgetArea' ).reduce( - ( widgetAreasObject, { id } ) => { - widgetAreasObject[ id ] = getEditedEntityRecord( - 'root', - 'widgetArea', - id - ).blocks; - return widgetAreasObject; - }, - {} - ); + const { getEditedEntityRecord } = window.wp.data.select( 'core' ); + const { getWidgetAreas } = window.wp.data.select( 'core/edit-widgets' ); + + return getWidgetAreas().reduce( ( widgetAreasObject, { id } ) => { + widgetAreasObject[ id ] = getEditedEntityRecord( + KIND, + WIDGET_ENTITY_TYPE, + id + ).blocks; + return widgetAreasObject; + }, {} ); }; // Serialize the provided blocks and render them in the widget area with the provided ID. @@ -142,21 +143,18 @@ if ( window.wp && window.wp.customize && window.wp.data ) { waitForSelectValue( () => window.wp.data - .select( 'core' ) - .hasFinishedResolution( 'getEntityRecords', [ - 'root', - 'widgetArea', - ] ), + .select( 'core/edit-widgets' ) + .hasResolvedWidgetAreas(), true, () => window.wp.data - .select( 'core' ) - .getEntityRecords( 'root', 'widgetArea' ) + .select( 'core/edit-widgets' ) + .getWidgetAreas() ).then( () => { Object.keys( widgetAreas ).forEach( ( id ) => { window.wp.data .dispatch( 'core' ) - .editEntityRecord( 'root', 'widgetArea', id, { + .editEntityRecord( KIND, WIDGET_ENTITY_TYPE, id, { content: serialize( widgetAreas[ id ] ), blocks: widgetAreas[ id ], } ); diff --git a/packages/edit-widgets/src/components/save-button/index.js b/packages/edit-widgets/src/components/save-button/index.js index 637aac055e635..53d1cd69400f8 100644 --- a/packages/edit-widgets/src/components/save-button/index.js +++ b/packages/edit-widgets/src/components/save-button/index.js @@ -11,21 +11,25 @@ import { __ } from '@wordpress/i18n'; import { useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { KIND, WIDGET_ENTITY_TYPE } from '../../store/utils'; + function SaveButton() { const { editedWidgetAreaIds, isSaving } = useSelect( ( select ) => { - const { - hasEditsForEntityRecord, - isSavingEntityRecord, - getEntityRecords, - } = select( 'core' ); - const widgetAreas = getEntityRecords( 'root', 'widgetArea' ); + const { hasEditsForEntityRecord, isSavingEntityRecord } = select( + 'core' + ); + const { getWidgetAreas } = select( 'core/edit-widgets' ); + const widgetAreas = getWidgetAreas(); const widgetAreaIds = map( widgetAreas, ( { id } ) => id ); return { editedWidgetAreaIds: filter( widgetAreaIds, ( id ) => - hasEditsForEntityRecord( 'root', 'widgetArea', id ) + hasEditsForEntityRecord( KIND, WIDGET_ENTITY_TYPE, id ) ), isSaving: some( widgetAreaIds, ( id ) => - isSavingEntityRecord( 'root', 'widgetArea', id ) + isSavingEntityRecord( KIND, WIDGET_ENTITY_TYPE, id ) ), }; }, [] ); @@ -33,7 +37,7 @@ function SaveButton() { const onClick = useCallback( () => { forEach( editedWidgetAreaIds, ( id ) => { - saveEditedEntityRecord( 'root', 'widgetArea', id ); + saveEditedEntityRecord( KIND, WIDGET_ENTITY_TYPE, id ); } ); }, [ editedWidgetAreaIds ] ); diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js index 21e91953e5bf6..6bb0795c36f5b 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js @@ -18,6 +18,7 @@ import KeyboardShortcuts from '../keyboard-shortcuts'; export default function WidgetAreasBlockEditorContent() { const { clearSelectedBlock } = useDispatch( 'core/block-editor' ); + return ( <> diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index c55d5ee2f2d9b..c2094f0bf86e0 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -31,10 +31,10 @@ export default function WidgetAreasBlockEditorProvider( { blockEditorSettings, ...props } ) { - const { sidebarsPosts, hasUploadPermissions } = useSelect( ( select ) => { - const { getSidebarsPosts } = select( 'core/edit-widgets' ); + const { widgetAreas, hasUploadPermissions } = useSelect( ( select ) => { + const { getWidgetAreas } = select( 'core/edit-widgets' ); return { - sidebarsPosts: getSidebarsPosts() || EMPTY_ARRAY, + widgetAreas: getWidgetAreas() || EMPTY_ARRAY, hasUploadPermissions: defaultTo( select( 'core' ).canUser( 'create', 'media' ), true @@ -44,15 +44,18 @@ export default function WidgetAreasBlockEditorProvider( { const [ blocks, setBlocks ] = useState( [] ); useEffect( () => { - if ( ! sidebarsPosts || ! sidebarsPosts.length || blocks.length > 0 ) { + if ( ! widgetAreas || ! widgetAreas.length || blocks.length > 0 ) { return; } setBlocks( - sidebarsPosts.map( ( post ) => { - return post.blocks[ 0 ]; + widgetAreas.map( ( { id, name } ) => { + return createBlock( 'core/widget-area', { + id, + name, + } ); } ) ); - }, [ sidebarsPosts, blocks ] ); + }, [ widgetAreas, blocks ] ); const settings = useMemo( () => { let mediaUploadBlockEditor; diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 4e5adccb4db2a..ff9bc5d166bf3 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -12,6 +12,7 @@ import { * Internal dependencies */ import './store'; +export { buildSidebarPostId } from './store/utils'; import './hooks'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; import CustomizerEditWidgetsInitializer from './components/customizer-edit-widgets-initializer'; diff --git a/packages/edit-widgets/src/store/controls.js b/packages/edit-widgets/src/store/controls.js index 639350e900c89..9d0c6d1ac478c 100644 --- a/packages/edit-widgets/src/store/controls.js +++ b/packages/edit-widgets/src/store/controls.js @@ -7,7 +7,7 @@ import { createRegistryControl } from '@wordpress/data'; /** * Internal dependencies */ -import { buildSidebarsPostsQuery } from './utils'; +import { buildWidgetAreasQuery } from './utils'; /** * Trigger an API Fetch request. @@ -85,10 +85,10 @@ export function getNavigationPostForMenu( menuId ) { * @param query * @return {Object} Action. */ -export function resolveSidebars() { +export function resolveWidgetAreas( query = buildWidgetAreasQuery() ) { return { type: 'RESOLVE_SIDEBARS', - query: {}, + query, }; } @@ -167,7 +167,7 @@ const controls = { RESOLVE_SIDEBARS: createRegistryControl( ( registry ) => ( { query } ) => { return registry .__experimentalResolveSelect( 'core' ) - .getSidebars( query ); + .getEntityRecords( 'root', 'widgetArea', query ); //getWidgetAreas( query ); } ), }; diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index b61085f175753..251465a903403 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -1,71 +1,38 @@ /** * WordPress dependencies */ -import { parse, createBlock } from '@wordpress/blocks'; +import { parse, createBlock, serialize } from '@wordpress/blocks'; /** * Internal dependencies */ -import { resolveSidebars, select, dispatch } from './controls'; -import { - KIND, - POST_TYPE, - buildSidebarsPostsQuery, - buildSidebarPostId, -} from './utils'; - -export function* getSidebarsPosts() { - const postsQuery = buildSidebarsPostsQuery(); - - // Dispatch startResolution to skip the execution of the real getEntityRecord resolver - it would - // issue an http request and fail. - const args = [ KIND, POST_TYPE, postsQuery ]; - yield dispatch( 'core', 'startResolution', 'getEntityRecords', args ); - - yield resolveSidebars(); - const sidebars = yield select( 'core', 'getSidebars' ); - const posts = sidebars.map( ( sidebar ) => { - const widgetAreaBlock = createWidgetAreaBlock( sidebar ); - return createStubPost( sidebar.id, widgetAreaBlock ); - } ); - - yield persistPosts( posts, postsQuery ); - - // Dispatch finishResolution to conclude startResolution dispatched earlier - yield dispatch( 'core', 'finishResolution', 'getEntityRecords', args ); -} - -const createStubPost = ( sidebarId, widgetAreaBlock ) => { - const id = buildSidebarPostId( sidebarId ); - return { - id, - slug: id, - status: 'draft', - type: 'sidebar-page', - blocks: [ widgetAreaBlock ], - meta: { - sidebarId, - }, - }; -}; - -const persistPosts = ( posts, query ) => - dispatch( 'core', 'receiveEntityRecords', KIND, POST_TYPE, posts, query ); - -/** - * Converts a list of widgets into a widget-area block. - * - * @param {Array} sidebar a list of widgets - * @return {Object} Navigation block - */ -function createWidgetAreaBlock( sidebar ) { - return createBlock( - 'core/widget-area', - { - id: sidebar.id, - name: sidebar.name, - }, - sidebar.widgets.flatMap( convertWidgetToBlock ) +import { resolveWidgetAreas, select, dispatch } from './controls'; +import { KIND, WIDGET_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; + +export function* getWidgetAreas() { + const query = buildWidgetAreasQuery(); + yield resolveWidgetAreas( query ); + let widgetAreas = yield select( + 'core', + 'getEntityRecords', + KIND, + WIDGET_ENTITY_TYPE, + query + ); + widgetAreas = widgetAreas.map( ( sidebar ) => ( { + ...sidebar, + content: serialize( + ( sidebar.widgets || [] ).flatMap( convertWidgetToBlock ) + ), + } ) ); + + yield dispatch( + 'core', + 'receiveEntityRecords', + KIND, + WIDGET_ENTITY_TYPE, + widgetAreas, + query ); } @@ -84,6 +51,9 @@ function convertWidgetToBlock( widget ) { 'core/legacy-widget', { id: widget.id, + widgetClass: widget.widget_class, + rendered: widget.rendered, + form: widget.form, }, [] ); diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index 3275c169d88f7..6a2cd5d44be9c 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -6,7 +6,7 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { KIND, POST_TYPE, buildSidebarsPostsQuery } from './utils'; +import { KIND, WIDGET_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; /** * Returns a "stub" sidebar post reflecting the contents of a sidebar with id=sidebarId. The @@ -16,18 +16,16 @@ import { KIND, POST_TYPE, buildSidebarsPostsQuery } from './utils'; * @param {number} menuId The id sidebar menu to create a post from. * @return {null|Object} Post once the resolver fetches it, otherwise null */ -export const getSidebarsPosts = createRegistrySelector( - ( select ) => ( state ) => { - // When the record is unavailable, calling getEditedEntityRecord triggers a http - // request via it's related resolver. Let's return nothing until getNavigationPostForMenu - // resolver marks the record as resolved. - const postsQuery = buildSidebarsPostsQuery(); - if ( ! hasResolvedSidebarsPost( state, postsQuery ) ) { - return null; - } - return select( 'core' ).getEntityRecords( KIND, POST_TYPE, postsQuery ); +export const getWidgetAreas = createRegistrySelector( ( select ) => () => { + if ( ! hasResolvedWidgetAreas() ) { + return null; } -); + + const query = buildWidgetAreasQuery(); + return select( 'core' ) + .getEntityRecords( KIND, WIDGET_ENTITY_TYPE, query ) + .filter( ( { id } ) => id !== 'wp_inactive_widgets' ); +} ); /** * Returns true if the navigation post related to menuId was already resolved. @@ -35,12 +33,30 @@ export const getSidebarsPosts = createRegistrySelector( * @param {number} menuId The id of menu. * @return {boolean} True if the navigation post related to menuId was already resolved, false otherwise. */ -export const hasResolvedSidebarsPost = createRegistrySelector( - ( select ) => ( state, query ) => { - return select( 'core' ).hasFinishedResolution( 'getEntityRecords', [ +export const hasResolvedWidgetAreas = createRegistrySelector( + ( select ) => () => { + const query = buildWidgetAreasQuery(); + const resolutionFinished = select( + 'core' + ).hasFinishedResolution( 'getEntityRecords', [ KIND, - POST_TYPE, + WIDGET_ENTITY_TYPE, query, ] ); + if ( ! resolutionFinished ) { + return false; + } + + const areas = select( 'core' ).getEntityRecords( + KIND, + WIDGET_ENTITY_TYPE, + query + ); + const contentAssigned = ! areas.length || 'content' in areas[ 0 ]; + if ( ! contentAssigned ) { + return false; + } + + return true; } ); diff --git a/packages/edit-widgets/src/store/utils.js b/packages/edit-widgets/src/store/utils.js index 85726cdb069a6..5c613953edb6c 100644 --- a/packages/edit-widgets/src/store/utils.js +++ b/packages/edit-widgets/src/store/utils.js @@ -10,21 +10,13 @@ export const KIND = 'root'; * * @type {string} */ -export const POST_TYPE = 'postType'; - -/** - * Builds an ID for a new navigation post. - * - * @param {number} menuId Menu id. - * @return {string} An ID. - */ -export const buildSidebarPostId = ( menuId ) => `sidebar-post-${ menuId }`; +export const WIDGET_ENTITY_TYPE = 'widgetArea'; /** * Builds a query to resolve sidebars. * * @return {Object} Query. */ -export function buildSidebarsPostsQuery() { - return { type: 'sidebar-page' }; +export function buildWidgetAreasQuery() { + return {}; } From feedcbd39d0e830d86d05363c97c934581ec70ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 31 Jul 2020 12:26:42 +0200 Subject: [PATCH 03/41] Fully functional blocks rendering --- lib/class-experimental-wp-widget-blocks-manager.php | 2 +- packages/edit-widgets/src/store/resolvers.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/class-experimental-wp-widget-blocks-manager.php b/lib/class-experimental-wp-widget-blocks-manager.php index c915807bd769b..db9337b54c7eb 100644 --- a/lib/class-experimental-wp-widget-blocks-manager.php +++ b/lib/class-experimental-wp-widget-blocks-manager.php @@ -187,7 +187,7 @@ private static function get_widget_class( $widget_id ) { * @param string $id Identifier of the widget instance. * @return array Array containing the widget instance. */ - private static function get_sidebar_widget_instance( $sidebar, $id ) { + public static function get_sidebar_widget_instance( $sidebar, $id ) { list( $object, $number, $name ) = self::get_widget_info( $id ); if ( ! $object ) { return array(); diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index 251465a903403..5c96eb665656a 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -46,12 +46,15 @@ function convertWidgetToBlock( widget ) { } } - // @TODO: reuse the rendered string we got from the API return createBlock( 'core/legacy-widget', { id: widget.id, widgetClass: widget.widget_class, + instance: widget.settings, + idBase: widget.id_base, + number: widget.number, + rendered: widget.rendered, form: widget.form, }, From 46dc63471654da34a2617b07cbd98641d0b2d6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 31 Jul 2020 12:58:38 +0200 Subject: [PATCH 04/41] Add a scaffold of store-based saving --- packages/core-data/src/resolvers.js | 2 +- .../sync-customizer.js | 6 +-- .../src/components/save-button/index.js | 37 ++++---------- packages/edit-widgets/src/store/actions.js | 35 +++++++++++++ packages/edit-widgets/src/store/resolvers.js | 6 +-- packages/edit-widgets/src/store/selectors.js | 50 +++++++++++++++++-- packages/edit-widgets/src/store/utils.js | 2 +- 7 files changed, 98 insertions(+), 40 deletions(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 96a87f9a79a7b..076cecc2e7a26 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -54,7 +54,7 @@ export function* getCurrentUser() { export function* getEntityRecord( kind, name, key = '' ) { const entities = yield getKindEntities( kind ); const entity = find( entities, { kind, name } ); - if ( ! entity ) { + if ( entity ) { return; } const record = yield apiFetch( { diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js index cb6e268f7c335..6c26d5c7be5d2 100644 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js @@ -11,7 +11,7 @@ import { parse, serialize } from '@wordpress/blocks'; /** * Internal dependencies */ -import { KIND, WIDGET_ENTITY_TYPE } from '../../store/utils'; +import { KIND, WIDGET_AREA_ENTITY_TYPE } from '../../store/utils'; /* Widget area edits made in the Customizer are synced to Customizer @@ -59,7 +59,7 @@ const getWidgetAreasObject = () => { return getWidgetAreas().reduce( ( widgetAreasObject, { id } ) => { widgetAreasObject[ id ] = getEditedEntityRecord( KIND, - WIDGET_ENTITY_TYPE, + WIDGET_AREA_ENTITY_TYPE, id ).blocks; return widgetAreasObject; @@ -154,7 +154,7 @@ if ( window.wp && window.wp.customize && window.wp.data ) { Object.keys( widgetAreas ).forEach( ( id ) => { window.wp.data .dispatch( 'core' ) - .editEntityRecord( KIND, WIDGET_ENTITY_TYPE, id, { + .editEntityRecord( KIND, WIDGET_AREA_ENTITY_TYPE, id, { content: serialize( widgetAreas[ id ] ), blocks: widgetAreas[ id ], } ); diff --git a/packages/edit-widgets/src/components/save-button/index.js b/packages/edit-widgets/src/components/save-button/index.js index 53d1cd69400f8..5fe82069abc51 100644 --- a/packages/edit-widgets/src/components/save-button/index.js +++ b/packages/edit-widgets/src/components/save-button/index.js @@ -1,53 +1,34 @@ -/** - * External dependencies - */ -import { filter, map, some, forEach } from 'lodash'; - /** * WordPress dependencies */ import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ -import { KIND, WIDGET_ENTITY_TYPE } from '../../store/utils'; function SaveButton() { - const { editedWidgetAreaIds, isSaving } = useSelect( ( select ) => { - const { hasEditsForEntityRecord, isSavingEntityRecord } = select( - 'core' + const { hasEditedWidgetAreaIds, isSaving } = useSelect( ( select ) => { + const { getEditedWidgetAreas, isSavingWidgetAreas } = select( + 'core/edit-widgets' ); - const { getWidgetAreas } = select( 'core/edit-widgets' ); - const widgetAreas = getWidgetAreas(); - const widgetAreaIds = map( widgetAreas, ( { id } ) => id ); + return { - editedWidgetAreaIds: filter( widgetAreaIds, ( id ) => - hasEditsForEntityRecord( KIND, WIDGET_ENTITY_TYPE, id ) - ), - isSaving: some( widgetAreaIds, ( id ) => - isSavingEntityRecord( KIND, WIDGET_ENTITY_TYPE, id ) - ), + hasEditedWidgetAreaIds: getEditedWidgetAreas()?.length > 0, + isSaving: isSavingWidgetAreas(), }; }, [] ); - const { saveEditedEntityRecord } = useDispatch( 'core' ); - - const onClick = useCallback( () => { - forEach( editedWidgetAreaIds, ( id ) => { - saveEditedEntityRecord( KIND, WIDGET_ENTITY_TYPE, id ); - } ); - }, [ editedWidgetAreaIds ] ); + const { saveEditedWidgetAreas } = useDispatch( 'core/edit-widgets' ); return ( diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index e69de29bb2d1d..bfb6ba0a03a59 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { parse, serialize } from '@wordpress/blocks'; +/** + * Internal dependencies + */ +import { resolveWidgetAreas, select, dispatch } from './controls'; + +export function* saveEditedWidgetAreas() { + const widgetAreas = yield select( + 'core/edit-widgets', + 'getEditedWidgetAreas' + ); + if ( ! widgetAreas ) { + return; + } + + // @TODO: Batch save + for ( const widgetArea in widgetAreas ) { + // @TODO: Concurrency? + yield* saveWidgetArea( widgetArea ); + } +} + +export function* saveWidgetArea( widgetArea ) { + console.log( 'saving widget area', widgetArea ); + // return; + // const blocks = parse( widgetArea.content ); + // const widgets = blocks.map( convertBlockToWidget ); + // Update the entity with widgets list + // Save the entity by sending an API request +} + +// function convertBlockToWidget( block ) {} diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index 5c96eb665656a..03eb3c418aca7 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -7,7 +7,7 @@ import { parse, createBlock, serialize } from '@wordpress/blocks'; * Internal dependencies */ import { resolveWidgetAreas, select, dispatch } from './controls'; -import { KIND, WIDGET_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; +import { KIND, WIDGET_AREA_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; export function* getWidgetAreas() { const query = buildWidgetAreasQuery(); @@ -16,7 +16,7 @@ export function* getWidgetAreas() { 'core', 'getEntityRecords', KIND, - WIDGET_ENTITY_TYPE, + WIDGET_AREA_ENTITY_TYPE, query ); widgetAreas = widgetAreas.map( ( sidebar ) => ( { @@ -30,7 +30,7 @@ export function* getWidgetAreas() { 'core', 'receiveEntityRecords', KIND, - WIDGET_ENTITY_TYPE, + WIDGET_AREA_ENTITY_TYPE, widgetAreas, query ); diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index 6a2cd5d44be9c..f3425be0f22b8 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -6,7 +6,7 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { KIND, WIDGET_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; +import { KIND, WIDGET_AREA_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; /** * Returns a "stub" sidebar post reflecting the contents of a sidebar with id=sidebarId. The @@ -23,10 +23,52 @@ export const getWidgetAreas = createRegistrySelector( ( select ) => () => { const query = buildWidgetAreasQuery(); return select( 'core' ) - .getEntityRecords( KIND, WIDGET_ENTITY_TYPE, query ) + .getEntityRecords( KIND, WIDGET_AREA_ENTITY_TYPE, query ) .filter( ( { id } ) => id !== 'wp_inactive_widgets' ); } ); +export const getEditedWidgetAreas = createRegistrySelector( + ( select ) => ( state, ids ) => { + let widgetAreas = select( 'core/edit-widgets' ).getWidgetAreas(); + if ( ! widgetAreas ) { + return []; + } + if ( ids ) { + widgetAreas = widgetAreas.filter( ( { id } ) => + ids.includes( id ) + ); + } + return widgetAreas.filter( ( { id } ) => + select( 'core' ).hasEditsForEntityRecord( + KIND, + WIDGET_AREA_ENTITY_TYPE, + id + ) + ); + } +); + +export const isSavingWidgetAreas = createRegistrySelector( + ( select ) => ( state, ids ) => { + if ( ! ids ) { + ids = select( 'core/edit-widgets' ) + .getWidgetAreas() + ?.map( ( { id } ) => id ); + } + for ( const id in ids ) { + const isSaving = select( 'core' ).isSavingEntityRecord( + KIND, + WIDGET_AREA_ENTITY_TYPE, + id + ); + if ( isSaving ) { + return true; + } + } + return false; + } +); + /** * Returns true if the navigation post related to menuId was already resolved. * @@ -40,7 +82,7 @@ export const hasResolvedWidgetAreas = createRegistrySelector( 'core' ).hasFinishedResolution( 'getEntityRecords', [ KIND, - WIDGET_ENTITY_TYPE, + WIDGET_AREA_ENTITY_TYPE, query, ] ); if ( ! resolutionFinished ) { @@ -49,7 +91,7 @@ export const hasResolvedWidgetAreas = createRegistrySelector( const areas = select( 'core' ).getEntityRecords( KIND, - WIDGET_ENTITY_TYPE, + WIDGET_AREA_ENTITY_TYPE, query ); const contentAssigned = ! areas.length || 'content' in areas[ 0 ]; diff --git a/packages/edit-widgets/src/store/utils.js b/packages/edit-widgets/src/store/utils.js index 5c613953edb6c..381c577fc6627 100644 --- a/packages/edit-widgets/src/store/utils.js +++ b/packages/edit-widgets/src/store/utils.js @@ -10,7 +10,7 @@ export const KIND = 'root'; * * @type {string} */ -export const WIDGET_ENTITY_TYPE = 'widgetArea'; +export const WIDGET_AREA_ENTITY_TYPE = 'widgetArea'; /** * Builds a query to resolve sidebars. From 560b6db39cc5b66bcf2120f49554d28ecb00ef62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 4 Aug 2020 13:16:29 +0200 Subject: [PATCH 05/41] Sidebars API -based widget management --- gutenberg.php | 63 +++++++++++++++++++ .../index.js | 10 ++- packages/edit-widgets/src/store/actions.js | 42 ++++++++----- packages/edit-widgets/src/store/controls.js | 16 +++-- packages/edit-widgets/src/store/reducer.js | 22 ++++++- packages/edit-widgets/src/store/resolvers.js | 53 ++++++---------- packages/edit-widgets/src/store/selectors.js | 44 ++++++++----- .../edit-widgets/src/store/transformers.js | 50 +++++++++++++++ 8 files changed, 225 insertions(+), 75 deletions(-) create mode 100644 packages/edit-widgets/src/store/transformers.js diff --git a/gutenberg.php b/gutenberg.php index 2a4f5355295ef..7e412b491f60b 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -188,3 +188,66 @@ function gutenberg_register_widgets() { } add_action( 'widgets_init', 'gutenberg_register_widgets' ); + +function gutenberg_capture_menu_items( $menu_items, $args ) { + $args->menu_items = $menu_items; + return $menu_items; +} +add_filter( 'wp_nav_menu_objects', 'gutenberg_capture_menu_items', 10, 2 ); + +function gutenberg_convert_menu_item_to_block( $menu_item, &$menu_items_by_parent_id ) { + if ( 'html' === $menu_item->type ) { + return parse_blocks( $menu_item->content )[0]; + } + + $inner_blocks = array(); + + if ( isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ) { + foreach ( $menu_items_by_parent_id[ $menu_item->ID ] as $child_menu_item ) { + $inner_blocks[] = gutenberg_convert_menu_item_to_block( + $child_menu_item, + $menu_items_by_parent_id + ); + } + } + + return array( + 'blockName' => 'core/navigation-link', + 'attrs' => array( + 'label' => $menu_item->title, + 'url' => $menu_item->url, + ), + 'innerBlocks' => $inner_blocks, + ); +} + +function gutenberg_handle_block_nav_menu( $nav_menu, $args ) { + if ( ! current_theme_supports( 'block-nav-menus' ) ) { + return $nav_menu; + } + + $menu_items_by_parent_id = array(); + foreach ( $args->menu_items as $menu_item ) { + $menu_items_by_parent_id[ intval( $menu_item->menu_item_parent ) ][] = $menu_item; + } + + $inner_blocks = array(); + + if ( isset( $menu_items_by_parent_id[0] ) ) { + foreach ( $menu_items_by_parent_id[0] as $menu_item ) { + $inner_blocks[] = gutenberg_convert_menu_item_to_block( + $menu_item, + $menu_items_by_parent_id + ); + } + } + + $navigation_block = array( + 'blockName' => 'core/navigation', + 'attrs' => array(), + 'innerBlocks' => $inner_blocks, + ); + + return render_block( $navigation_block ); +} +add_filter( 'wp_nav_menu', 'gutenberg_handle_block_nav_menu', 10, 2 ); diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index c2094f0bf86e0..f4af70f160b04 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -84,8 +84,14 @@ export default function WidgetAreasBlockEditorProvider( { setBlocks( newBlocks ) } - onChange={ ( newBlocks ) => setBlocks( newBlocks ) } + onInput={ ( newBlocks ) => { + console.log( newBlocks ); + setBlocks( newBlocks ); + } } + onChange={ ( newBlocks ) => { + console.log( newBlocks ); + setBlocks( newBlocks ); + } } settings={ settings } { ...props } /> diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index bfb6ba0a03a59..df09a4772a595 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -1,35 +1,49 @@ /** - * WordPress dependencies + * External dependencies */ -import { parse, serialize } from '@wordpress/blocks'; +import { invert } from 'lodash'; + /** * Internal dependencies */ -import { resolveWidgetAreas, select, dispatch } from './controls'; +import { select, getWidgetToClientIdMapping } from './controls'; +import { transformBlockToWidget } from './transformers'; export function* saveEditedWidgetAreas() { - const widgetAreas = yield select( + const editedWidgetAreas = yield select( 'core/edit-widgets', 'getEditedWidgetAreas' ); - if ( ! widgetAreas ) { + if ( ! editedWidgetAreas?.length ) { return; } - // @TODO: Batch save - for ( const widgetArea in widgetAreas ) { - // @TODO: Concurrency? - yield* saveWidgetArea( widgetArea ); + const widgets = yield select( 'core/edit-widgets', 'getWidgets' ); + const widgetIdToClientId = yield getWidgetToClientIdMapping(); + const clientIdToWidgetId = invert( widgetIdToClientId ); + console.log( + widgets, + widgetIdToClientId, + clientIdToWidgetId + ) + + // @TODO: Batch save and concurrency + for ( const widgetArea of editedWidgetAreas ) { + const areaWidgets = widgetArea.blocks.map( ( block ) => { + const widgetId = clientIdToWidgetId[ block.clientId ]; + const oldWidget = widgets[ widgetId ]; + const newWidget = transformBlockToWidget( block, oldWidget ); + console.log( block.clientId, oldWidget, newWidget ); + } ); + + // yield* saveWidgetArea( widgetArea ); } } export function* saveWidgetArea( widgetArea ) { - console.log( 'saving widget area', widgetArea ); + const widgets = widgetArea.blocks.map( transformBlockToWidget ); + console.log( 'saving widget area', widgetArea.blocks, widgets ); // return; - // const blocks = parse( widgetArea.content ); - // const widgets = blocks.map( convertBlockToWidget ); // Update the entity with widgets list // Save the entity by sending an API request } - -// function convertBlockToWidget( block ) {} diff --git a/packages/edit-widgets/src/store/controls.js b/packages/edit-widgets/src/store/controls.js index 9d0c6d1ac478c..d6bad2dcecd19 100644 --- a/packages/edit-widgets/src/store/controls.js +++ b/packages/edit-widgets/src/store/controls.js @@ -50,15 +50,13 @@ export function isProcessingPost( postId ) { } /** - * Selects menuItemId -> clientId mapping (necessary for saving the navigation). + * Selects widgetId -> clientId mapping (necessary for saving the navigation). * - * @param {number} postId Navigation post ID. * @return {Object} Action. */ -export function getMenuItemToClientIdMapping( postId ) { +export function getWidgetToClientIdMapping() { return { - type: 'GET_MENU_ITEM_TO_CLIENT_ID_MAPPING', - postId, + type: 'GET_WIDGET_TO_CLIENT_ID_MAPPING', }; } @@ -152,9 +150,9 @@ const controls = { } ), - GET_MENU_ITEM_TO_CLIENT_ID_MAPPING: createRegistryControl( - ( registry ) => ( { postId } ) => { - return getState( registry ).mapping[ postId ] || {}; + GET_WIDGET_TO_CLIENT_ID_MAPPING: createRegistryControl( + ( registry ) => () => { + return getState( registry ).mapping || {}; } ), @@ -172,6 +170,6 @@ const controls = { }; const getState = ( registry ) => - registry.stores[ 'core/edit-navigation' ].store.getState(); + registry.stores[ 'core/edit-widgets' ].store.getState(); export default controls; diff --git a/packages/edit-widgets/src/store/reducer.js b/packages/edit-widgets/src/store/reducer.js index 841b6140ba2ed..7733f7f3a925a 100644 --- a/packages/edit-widgets/src/store/reducer.js +++ b/packages/edit-widgets/src/store/reducer.js @@ -3,4 +3,24 @@ */ import { combineReducers } from '@wordpress/data'; -export default combineReducers( {} ); +/** + * Internal to edit-widgets package. + * + * Stores widgetId -> clientId mapping which is necessary for saving the navigation. + * + * @param {Object} state Redux state + * @param {Object} action Redux action + * @return {Object} Updated state + */ +export function mapping( state, action ) { + const { type, ...rest } = action; + if ( type === 'SET_WIDGET_TO_CLIENT_ID_MAPPING' ) { + return rest.mapping; + } + + return state || {}; +} + +export default combineReducers( { + mapping, +} ); diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index 03eb3c418aca7..520698875edd2 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -1,30 +1,41 @@ /** * WordPress dependencies */ -import { parse, createBlock, serialize } from '@wordpress/blocks'; +import { serialize } from '@wordpress/blocks'; /** * Internal dependencies */ import { resolveWidgetAreas, select, dispatch } from './controls'; import { KIND, WIDGET_AREA_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; +import { transformWidgetToBlock } from './transformers'; export function* getWidgetAreas() { const query = buildWidgetAreasQuery(); yield resolveWidgetAreas( query ); - let widgetAreas = yield select( + const widgetAreas = yield select( 'core', 'getEntityRecords', KIND, WIDGET_AREA_ENTITY_TYPE, query ); - widgetAreas = widgetAreas.map( ( sidebar ) => ( { - ...sidebar, - content: serialize( - ( sidebar.widgets || [] ).flatMap( convertWidgetToBlock ) - ), - } ) ); + + const widgetIdToClientId = {}; + for ( const widgetArea of widgetAreas ) { + const blocks = []; + for ( const widget of widgetArea.widgets ) { + const block = transformWidgetToBlock( widget ); + widgetIdToClientId[ widget.id ] = block.clientId; + blocks.push( block ); + } + widgetArea.content = serialize( blocks ); + } + + yield { + type: 'SET_WIDGET_TO_CLIENT_ID_MAPPING', + mapping: widgetIdToClientId, + }; yield dispatch( 'core', @@ -35,29 +46,3 @@ export function* getWidgetAreas() { query ); } - -function convertWidgetToBlock( widget ) { - // @TODO: filter based on something that's less likely to change or be translated - if ( widget.name === 'Gutenberg Block' ) { - const parsedBlocks = parse( widget.settings.content ); - if ( ! parsedBlocks.length ) { - // @TODO: an empty block - return createBlock( 'core/paragraph', {}, [] ); - } - } - - return createBlock( - 'core/legacy-widget', - { - id: widget.id, - widgetClass: widget.widget_class, - instance: widget.settings, - idBase: widget.id_base, - number: widget.number, - - rendered: widget.rendered, - form: widget.form, - }, - [] - ); -} diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index f3425be0f22b8..882d86c3858fa 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { keyBy } from 'lodash'; + /** * WordPress dependencies */ @@ -6,16 +11,17 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { KIND, WIDGET_AREA_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; +import { buildWidgetAreasQuery, KIND, WIDGET_AREA_ENTITY_TYPE } from './utils'; + +export const getWidgets = createRegistrySelector( ( select ) => () => { + const initialWidgetAreas = select( 'core/edit-widgets' ).getWidgetAreas(); + + return keyBy( + initialWidgetAreas.flatMap( ( area ) => area.widgets ), + ( widget ) => widget.id + ); +} ); -/** - * Returns a "stub" sidebar post reflecting the contents of a sidebar with id=sidebarId. The - * post is meant as a convenient to only exists in runtime and should never be saved. It - * enables a convenient way of editing the navigation by using a regular post editor. - * - * @param {number} menuId The id sidebar menu to create a post from. - * @return {null|Object} Post once the resolver fetches it, otherwise null - */ export const getWidgetAreas = createRegistrySelector( ( select ) => () => { if ( ! hasResolvedWidgetAreas() ) { return null; @@ -38,13 +44,21 @@ export const getEditedWidgetAreas = createRegistrySelector( ids.includes( id ) ); } - return widgetAreas.filter( ( { id } ) => - select( 'core' ).hasEditsForEntityRecord( - KIND, - WIDGET_AREA_ENTITY_TYPE, - id + return widgetAreas + .filter( ( { id } ) => + select( 'core' ).hasEditsForEntityRecord( + KIND, + WIDGET_AREA_ENTITY_TYPE, + id + ) ) - ); + .map( ( { id } ) => + select( 'core' ).getEditedEntityRecord( + KIND, + WIDGET_AREA_ENTITY_TYPE, + id + ) + ); } ); diff --git a/packages/edit-widgets/src/store/transformers.js b/packages/edit-widgets/src/store/transformers.js new file mode 100644 index 0000000000000..fe41ded2cf2e6 --- /dev/null +++ b/packages/edit-widgets/src/store/transformers.js @@ -0,0 +1,50 @@ +/** + * WordPress dependencies + */ +import { createBlock, parse, serialize } from '@wordpress/blocks'; + +export function transformWidgetToBlock( widget ) { + if ( widget.widget_class === 'WP_Widget_Block' ) { + const parsedBlocks = parse( widget.settings.content ); + if ( ! parsedBlocks.length ) { + return createBlock( 'core/paragraph', {}, [] ); + } + return parsedBlocks[ 0 ]; + } + + return createBlock( + 'core/legacy-widget', + { + rendered: widget.rendered, + form: widget.form, + id: widget.id, + widgetClass: widget.widget_class, + instance: widget.settings, + idBase: widget.id_base, + number: widget.number, + }, + [] + ); +} + +export function transformBlockToWidget( block, relatedWidget = {} ) { + const { name, attributes } = block; + if ( name === 'core/legacy-widget' ) { + return { + id: attributes.id, + widget_class: attributes.widgetClass, + number: attributes.number, + id_base: attributes.idBase, + settings: attributes.instance, + }; + } + + return { + id: attributes.id, + widget_class: 'WP_Widget_Block', + number: attributes.number, + id_base: attributes.idBase, + settings: attributes.instance, + content: serialize( block ), + }; +} From 32e025a51e72082974be1a5cd5ded5c455d3a739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 4 Aug 2020 13:55:18 +0200 Subject: [PATCH 06/41] Entity-based save operation --- .../src/widget-area/edit/index.js | 16 +-- .../src/widget-area/edit/inner-blocks.js | 21 ---- .../src/components/layout/index.js | 53 ++++++--- .../index.js | 79 +++++++++++++- .../index.js | 103 ------------------ packages/edit-widgets/src/store/actions.js | 40 ++++--- packages/edit-widgets/src/store/resolvers.js | 13 ++- packages/edit-widgets/src/store/selectors.js | 7 +- .../edit-widgets/src/store/transformers.js | 15 ++- 9 files changed, 164 insertions(+), 183 deletions(-) delete mode 100644 packages/block-library/src/widget-area/edit/inner-blocks.js delete mode 100644 packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js diff --git a/packages/block-library/src/widget-area/edit/index.js b/packages/block-library/src/widget-area/edit/index.js index f25273433dcce..a89d50b38feb3 100644 --- a/packages/block-library/src/widget-area/edit/index.js +++ b/packages/block-library/src/widget-area/edit/index.js @@ -2,18 +2,13 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; -import { EntityProvider } from '@wordpress/core-data'; import { Panel, PanelBody } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import WidgetAreaInnerBlocks from './inner-blocks'; +import { InnerBlocks } from '@wordpress/block-editor'; export default function WidgetAreaEdit( { clientId, className, - attributes: { id, name }, + attributes: { name }, } ) { const index = useSelect( ( select ) => select( 'core/block-editor' ).getBlockIndex( clientId ), @@ -22,9 +17,10 @@ export default function WidgetAreaEdit( { return ( - - - + ); diff --git a/packages/block-library/src/widget-area/edit/inner-blocks.js b/packages/block-library/src/widget-area/edit/inner-blocks.js deleted file mode 100644 index 5e4b8eb31a70b..0000000000000 --- a/packages/block-library/src/widget-area/edit/inner-blocks.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * WordPress dependencies - */ -import { useEntityBlockEditor } from '@wordpress/core-data'; -import { InnerBlocks } from '@wordpress/block-editor'; - -export default function WidgetAreaInnerBlocks() { - const [ blocks, onInput, onChange ] = useEntityBlockEditor( - 'root', - 'widgetArea' - ); - return ( - - ); -} diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index b9ae278662f88..fd68665a05470 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -1,15 +1,22 @@ /** * WordPress dependencies */ -import { Popover } from '@wordpress/components'; -import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; +import { + DropZoneProvider, + SlotFillProvider, + FocusReturnProvider, + Popover, +} from '@wordpress/components'; import { useSelect } from '@wordpress/data'; +import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; +import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; + /** * Internal dependencies */ +import KeyboardShortcuts from '../keyboard-shortcuts'; import Header from '../header'; import Sidebar from '../sidebar'; -import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; function Layout( { blockEditorSettings } ) { @@ -19,21 +26,31 @@ function Layout( { blockEditorSettings } ) { ); } ); return ( - - } - sidebar={ - hasSidebarEnabled && ( - - ) - } - content={ } - /> - - - + <> + + + + + + } + sidebar={ + hasSidebarEnabled && ( + + ) + } + content={ + + } + /> + + + + + + ); } diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js index 6bb0795c36f5b..1a84f8e9c7a09 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js @@ -1,23 +1,40 @@ +/** + * External dependencies + */ +import { defaultTo } from 'lodash'; + /** * WordPress dependencies */ import { Popover } from '@wordpress/components'; +import { uploadMedia } from '@wordpress/media-utils'; import { + BlockEditorProvider, BlockEditorKeyboardShortcuts, WritingFlow, ObserveTyping, BlockList, } from '@wordpress/block-editor'; -import { useDispatch } from '@wordpress/data'; +import { useEntityBlockEditor } from '@wordpress/core-data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; /** * Internal dependencies */ import Notices from '../notices'; import KeyboardShortcuts from '../keyboard-shortcuts'; +import { KIND, WIDGET_AREA_ENTITY_TYPE } from '../../store/utils'; -export default function WidgetAreasBlockEditorContent() { +const EMPTY_ARRAY = []; +export default function WidgetAreasBlockEditorContent( { + blockEditorSettings, +} ) { const { clearSelectedBlock } = useDispatch( 'core/block-editor' ); + const widgetAreas = useSelect( ( select ) => { + const { getWidgetAreas } = select( 'core/edit-widgets' ); + return getWidgetAreas() || EMPTY_ARRAY; + } ); return ( <> @@ -37,7 +54,17 @@ export default function WidgetAreasBlockEditorContent() { > - + { widgetAreas.map( ( widgetArea ) => { + return ( + + ); + } ) } @@ -45,3 +72,49 @@ export default function WidgetAreasBlockEditorContent() { ); } + +function WidgetAreaEditor( { widgetArea, blockEditorSettings } ) { + const { hasUploadPermissions } = useSelect( ( select ) => { + return { + hasUploadPermissions: defaultTo( + select( 'core' ).canUser( 'create', 'media' ), + true + ), + }; + } ); + + const settings = useMemo( () => { + let mediaUploadBlockEditor; + if ( hasUploadPermissions ) { + mediaUploadBlockEditor = ( { onError, ...argumentsObject } ) => { + uploadMedia( { + wpAllowedMimeTypes: blockEditorSettings.allowedMimeTypes, + onError: ( { message } ) => onError( message ), + ...argumentsObject, + } ); + }; + } + return { + ...blockEditorSettings, + mediaUpload: mediaUploadBlockEditor, + templateLock: 'all', + }; + }, [ blockEditorSettings, hasUploadPermissions ] ); + + const [ blocks, onInput, _onChange ] = useEntityBlockEditor( + KIND, + WIDGET_AREA_ENTITY_TYPE, + { id: widgetArea.id } + ); + + return ( + + + + ); +} diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js deleted file mode 100644 index f4af70f160b04..0000000000000 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * External dependencies - */ -import { defaultTo } from 'lodash'; - -/** - * WordPress dependencies - */ -import { - DropZoneProvider, - SlotFillProvider, - FocusReturnProvider, -} from '@wordpress/components'; -import { uploadMedia } from '@wordpress/media-utils'; -import { useSelect } from '@wordpress/data'; -import { useEffect, useMemo, useState } from '@wordpress/element'; -import { createBlock } from '@wordpress/blocks'; -import { - BlockEditorProvider, - BlockEditorKeyboardShortcuts, -} from '@wordpress/block-editor'; - -/** - * Internal dependencies - */ -import KeyboardShortcuts from '../keyboard-shortcuts'; - -const EMPTY_ARRAY = []; - -export default function WidgetAreasBlockEditorProvider( { - blockEditorSettings, - ...props -} ) { - const { widgetAreas, hasUploadPermissions } = useSelect( ( select ) => { - const { getWidgetAreas } = select( 'core/edit-widgets' ); - return { - widgetAreas: getWidgetAreas() || EMPTY_ARRAY, - hasUploadPermissions: defaultTo( - select( 'core' ).canUser( 'create', 'media' ), - true - ), - }; - } ); - - const [ blocks, setBlocks ] = useState( [] ); - useEffect( () => { - if ( ! widgetAreas || ! widgetAreas.length || blocks.length > 0 ) { - return; - } - setBlocks( - widgetAreas.map( ( { id, name } ) => { - return createBlock( 'core/widget-area', { - id, - name, - } ); - } ) - ); - }, [ widgetAreas, blocks ] ); - - const settings = useMemo( () => { - let mediaUploadBlockEditor; - if ( hasUploadPermissions ) { - mediaUploadBlockEditor = ( { onError, ...argumentsObject } ) => { - uploadMedia( { - wpAllowedMimeTypes: blockEditorSettings.allowedMimeTypes, - onError: ( { message } ) => onError( message ), - ...argumentsObject, - } ); - }; - } - return { - ...blockEditorSettings, - mediaUpload: mediaUploadBlockEditor, - templateLock: 'all', - }; - }, [ blockEditorSettings, hasUploadPermissions ] ); - - return ( - <> - - - - - - { - console.log( newBlocks ); - setBlocks( newBlocks ); - } } - onChange={ ( newBlocks ) => { - console.log( newBlocks ); - setBlocks( newBlocks ); - } } - settings={ settings } - { ...props } - /> - - - - - ); -} diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index df09a4772a595..0a18088c6da7c 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -6,8 +6,9 @@ import { invert } from 'lodash'; /** * Internal dependencies */ -import { select, getWidgetToClientIdMapping } from './controls'; +import { dispatch, select, getWidgetToClientIdMapping } from './controls'; import { transformBlockToWidget } from './transformers'; +import { KIND, WIDGET_AREA_ENTITY_TYPE } from './utils'; export function* saveEditedWidgetAreas() { const editedWidgetAreas = yield select( @@ -21,29 +22,36 @@ export function* saveEditedWidgetAreas() { const widgets = yield select( 'core/edit-widgets', 'getWidgets' ); const widgetIdToClientId = yield getWidgetToClientIdMapping(); const clientIdToWidgetId = invert( widgetIdToClientId ); - console.log( - widgets, - widgetIdToClientId, - clientIdToWidgetId - ) // @TODO: Batch save and concurrency for ( const widgetArea of editedWidgetAreas ) { - const areaWidgets = widgetArea.blocks.map( ( block ) => { + const widgetAreaBlock = widgetArea.blocks[ 0 ]; + const widgetsBlocks = widgetAreaBlock.innerBlocks; + const newWidgets = widgetsBlocks.map( ( block ) => { const widgetId = clientIdToWidgetId[ block.clientId ]; const oldWidget = widgets[ widgetId ]; - const newWidget = transformBlockToWidget( block, oldWidget ); - console.log( block.clientId, oldWidget, newWidget ); + return transformBlockToWidget( block, oldWidget ); } ); - // yield* saveWidgetArea( widgetArea ); + yield* saveWidgetArea( widgetArea, newWidgets ); } } -export function* saveWidgetArea( widgetArea ) { - const widgets = widgetArea.blocks.map( transformBlockToWidget ); - console.log( 'saving widget area', widgetArea.blocks, widgets ); - // return; - // Update the entity with widgets list - // Save the entity by sending an API request +export function* saveWidgetArea( widgetArea, newWidgets ) { + yield dispatch( + 'core', + 'editEntityRecord', + KIND, + WIDGET_AREA_ENTITY_TYPE, + widgetArea.id, + { widgets: newWidgets } + ); + + yield dispatch( + 'core', + 'saveEditedEntityRecord', + KIND, + WIDGET_AREA_ENTITY_TYPE, + widgetArea.id + ); } diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index 520698875edd2..4f076515ebd71 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { serialize } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -29,7 +29,16 @@ export function* getWidgetAreas() { widgetIdToClientId[ widget.id ] = block.clientId; blocks.push( block ); } - widgetArea.content = serialize( blocks ); + widgetArea.blocks = [ + createBlock( + 'core/widget-area', + { + id: widgetArea.id, + name: widgetArea.name, + }, + blocks + ), + ]; } yield { diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index 882d86c3858fa..9e4ce09bc2f7a 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -23,7 +23,7 @@ export const getWidgets = createRegistrySelector( ( select ) => () => { } ); export const getWidgetAreas = createRegistrySelector( ( select ) => () => { - if ( ! hasResolvedWidgetAreas() ) { + if ( ! hasResolvedWidgetAreas( query ) ) { return null; } @@ -90,8 +90,7 @@ export const isSavingWidgetAreas = createRegistrySelector( * @return {boolean} True if the navigation post related to menuId was already resolved, false otherwise. */ export const hasResolvedWidgetAreas = createRegistrySelector( - ( select ) => () => { - const query = buildWidgetAreasQuery(); + ( select, query = buildWidgetAreasQuery() ) => () => { const resolutionFinished = select( 'core' ).hasFinishedResolution( 'getEntityRecords', [ @@ -108,7 +107,7 @@ export const hasResolvedWidgetAreas = createRegistrySelector( WIDGET_AREA_ENTITY_TYPE, query ); - const contentAssigned = ! areas.length || 'content' in areas[ 0 ]; + const contentAssigned = ! areas.length || 'blocks' in areas[ 0 ]; if ( ! contentAssigned ) { return false; } diff --git a/packages/edit-widgets/src/store/transformers.js b/packages/edit-widgets/src/store/transformers.js index fe41ded2cf2e6..9adfca7c8feab 100644 --- a/packages/edit-widgets/src/store/transformers.js +++ b/packages/edit-widgets/src/store/transformers.js @@ -30,21 +30,24 @@ export function transformWidgetToBlock( widget ) { export function transformBlockToWidget( block, relatedWidget = {} ) { const { name, attributes } = block; if ( name === 'core/legacy-widget' ) { - return { + const widget = { + ...relatedWidget, id: attributes.id, widget_class: attributes.widgetClass, number: attributes.number, id_base: attributes.idBase, settings: attributes.instance, }; + delete widget.form; + delete widget.rendered; + return widget; } return { - id: attributes.id, + ...relatedWidget, widget_class: 'WP_Widget_Block', - number: attributes.number, - id_base: attributes.idBase, - settings: attributes.instance, - content: serialize( block ), + settings: { + content: serialize( block ), + }, }; } From 638dadf3e675c39d6be7a280f9ea2deb1998fd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 4 Aug 2020 17:35:13 +0200 Subject: [PATCH 07/41] Enable REST-based widgets save operation --- lib/widgets.php | 2 + .../src/legacy-widget/edit/index.js | 8 ++- packages/core-data/src/entities.js | 2 +- .../index.js | 6 +- packages/edit-widgets/src/store/actions.js | 53 +++++++++------- packages/edit-widgets/src/store/resolvers.js | 62 ++++++++++++++----- packages/edit-widgets/src/store/selectors.js | 15 +++-- .../edit-widgets/src/store/transformers.js | 1 + packages/edit-widgets/src/store/utils.js | 16 +++++ 9 files changed, 115 insertions(+), 50 deletions(-) diff --git a/lib/widgets.php b/lib/widgets.php index b7beb483d9119..a8235252ba428 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -129,6 +129,7 @@ function gutenberg_get_legacy_widget_settings() { 'WP_Nav_Menu_Widget', 'WP_Widget_Custom_HTML', ); + $core_widgets = array(); $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); $available_legacy_widgets = array(); @@ -137,6 +138,7 @@ function gutenberg_get_legacy_widget_settings() { foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { $available_legacy_widgets[ $class ] = array( 'name' => html_entity_decode( $widget_obj->name ), + 'id_base' => $widget_obj->id_base, // wp_widget_description is not being used because its input parameter is a Widget Id. // Widgets id's reference to a specific widget instance. // Here we are iterating on all the available widget classes even if no widget instance exists for them. diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index 1e19b34907372..f71ab5309e9fb 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -54,12 +54,14 @@ class LegacyWidgetEdit extends Component { hasPermissionsToManageWidgets } onChangeWidget={ ( newWidget ) => { - const { isReferenceWidget } = availableLegacyWidgets[ - newWidget - ]; + const { + isReferenceWidget, + id_base: idBase, + } = availableLegacyWidgets[ newWidget ]; setAttributes( { instance: {}, id: isReferenceWidget ? newWidget : undefined, + idBase, widgetClass: isReferenceWidget ? undefined : newWidget, diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 2870c3c4661f2..a3a0631049145 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -65,7 +65,7 @@ export const defaultEntities = [ { name: 'widgetArea', kind: 'root', - baseURL: '/wp-rest-api-sidebars/v1/sidebars?with-widgets', + baseURL: '/wp-rest-api-sidebars/v1/sidebars', plural: 'widgetAreas', transientEdits: { blocks: true }, label: __( 'Widget areas' ), diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js index 1a84f8e9c7a09..7d42a6a033d36 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js @@ -24,7 +24,7 @@ import { useMemo } from '@wordpress/element'; */ import Notices from '../notices'; import KeyboardShortcuts from '../keyboard-shortcuts'; -import { KIND, WIDGET_AREA_ENTITY_TYPE } from '../../store/utils'; +import { KIND, POST_TYPE, buildWidgetAreaPostId } from '../../store/utils'; const EMPTY_ARRAY = []; export default function WidgetAreasBlockEditorContent( { @@ -103,8 +103,8 @@ function WidgetAreaEditor( { widgetArea, blockEditorSettings } ) { const [ blocks, onInput, _onChange ] = useEntityBlockEditor( KIND, - WIDGET_AREA_ENTITY_TYPE, - { id: widgetArea.id } + POST_TYPE, + { id: buildWidgetAreaPostId( widgetArea.id ) } ); return ( diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 0a18088c6da7c..19ea67884a991 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -8,7 +8,13 @@ import { invert } from 'lodash'; */ import { dispatch, select, getWidgetToClientIdMapping } from './controls'; import { transformBlockToWidget } from './transformers'; -import { KIND, WIDGET_AREA_ENTITY_TYPE } from './utils'; +import { + buildWidgetAreaPostId, + buildWidgetAreasQuery, + KIND, + POST_TYPE, + WIDGET_AREA_ENTITY_TYPE, +} from './utils'; export function* saveEditedWidgetAreas() { const editedWidgetAreas = yield select( @@ -25,7 +31,14 @@ export function* saveEditedWidgetAreas() { // @TODO: Batch save and concurrency for ( const widgetArea of editedWidgetAreas ) { - const widgetAreaBlock = widgetArea.blocks[ 0 ]; + const post = yield select( + 'core', + 'getEditedEntityRecord', + KIND, + POST_TYPE, + buildWidgetAreaPostId( widgetArea.id ) + ); + const widgetAreaBlock = post.blocks[ 0 ]; const widgetsBlocks = widgetAreaBlock.innerBlocks; const newWidgets = widgetsBlocks.map( ( block ) => { const widgetId = clientIdToWidgetId[ block.clientId ]; @@ -33,25 +46,21 @@ export function* saveEditedWidgetAreas() { return transformBlockToWidget( block, oldWidget ); } ); - yield* saveWidgetArea( widgetArea, newWidgets ); - } -} - -export function* saveWidgetArea( widgetArea, newWidgets ) { - yield dispatch( - 'core', - 'editEntityRecord', - KIND, - WIDGET_AREA_ENTITY_TYPE, - widgetArea.id, - { widgets: newWidgets } - ); + yield dispatch( + 'core', + 'editEntityRecord', + KIND, + WIDGET_AREA_ENTITY_TYPE, + widgetArea.id, + { widgets: newWidgets } + ); - yield dispatch( - 'core', - 'saveEditedEntityRecord', - KIND, - WIDGET_AREA_ENTITY_TYPE, - widgetArea.id - ); + yield dispatch( + 'core', + 'saveEditedEntityRecord', + KIND, + WIDGET_AREA_ENTITY_TYPE, + widgetArea.id + ); + } } diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index 4f076515ebd71..a8be42d326bb9 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -7,7 +7,13 @@ import { createBlock } from '@wordpress/blocks'; * Internal dependencies */ import { resolveWidgetAreas, select, dispatch } from './controls'; -import { KIND, WIDGET_AREA_ENTITY_TYPE, buildWidgetAreasQuery } from './utils'; +import { + KIND, + POST_TYPE, + WIDGET_AREA_ENTITY_TYPE, + buildWidgetAreasQuery, + buildWidgetAreaPostId, +} from './utils'; import { transformWidgetToBlock } from './transformers'; export function* getWidgetAreas() { @@ -29,29 +35,53 @@ export function* getWidgetAreas() { widgetIdToClientId[ widget.id ] = block.clientId; blocks.push( block ); } - widgetArea.blocks = [ - createBlock( - 'core/widget-area', - { - id: widgetArea.id, - name: widgetArea.name, - }, - blocks - ), - ]; + + // Persist the actual post containing the navigation block + const widgetAreaBlock = createBlock( + 'core/widget-area', + { + id: widgetArea.id, + name: widgetArea.name, + }, + blocks + ); + + // Dispatch startResolution and finishResolution to skip the execution of the real getEntityRecord resolver - it would + // issue an http request and fail. + const stubPost = createStubPost( widgetArea.id, widgetAreaBlock ); + const args = [ KIND, POST_TYPE, stubPost.id ]; + yield dispatch( 'core', 'startResolution', 'getEntityRecord', args ); + yield persistPost( stubPost ); + yield dispatch( 'core', 'finishResolution', 'getEntityRecord', args ); } yield { type: 'SET_WIDGET_TO_CLIENT_ID_MAPPING', mapping: widgetIdToClientId, }; +} + +const createStubPost = ( widgetAreaId, widgetAreaBlock ) => { + const id = buildWidgetAreaPostId( widgetAreaId ); + return { + id, + slug: id, + status: 'draft', + type: 'page', + blocks: [ widgetAreaBlock ], + meta: { + widgetAreaId, + }, + }; +}; - yield dispatch( +const persistPost = ( post ) => + dispatch( 'core', 'receiveEntityRecords', KIND, - WIDGET_AREA_ENTITY_TYPE, - widgetAreas, - query + POST_TYPE, + post, + { id: post.id }, + false ); -} diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index 9e4ce09bc2f7a..6b5b1f42531d3 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -11,7 +11,13 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ -import { buildWidgetAreasQuery, KIND, WIDGET_AREA_ENTITY_TYPE } from './utils'; +import { + buildWidgetAreasQuery, + buildWidgetAreaPostId, + KIND, + POST_TYPE, + WIDGET_AREA_ENTITY_TYPE, +} from './utils'; export const getWidgets = createRegistrySelector( ( select ) => () => { const initialWidgetAreas = select( 'core/edit-widgets' ).getWidgetAreas(); @@ -48,8 +54,8 @@ export const getEditedWidgetAreas = createRegistrySelector( .filter( ( { id } ) => select( 'core' ).hasEditsForEntityRecord( KIND, - WIDGET_AREA_ENTITY_TYPE, - id + POST_TYPE, + buildWidgetAreaPostId( id ) ) ) .map( ( { id } ) => @@ -107,8 +113,7 @@ export const hasResolvedWidgetAreas = createRegistrySelector( WIDGET_AREA_ENTITY_TYPE, query ); - const contentAssigned = ! areas.length || 'blocks' in areas[ 0 ]; - if ( ! contentAssigned ) { + if ( ! areas.length ) { return false; } diff --git a/packages/edit-widgets/src/store/transformers.js b/packages/edit-widgets/src/store/transformers.js index 9adfca7c8feab..ea855b267edab 100644 --- a/packages/edit-widgets/src/store/transformers.js +++ b/packages/edit-widgets/src/store/transformers.js @@ -45,6 +45,7 @@ export function transformBlockToWidget( block, relatedWidget = {} ) { return { ...relatedWidget, + id_base: 'block', widget_class: 'WP_Widget_Block', settings: { content: serialize( block ), diff --git a/packages/edit-widgets/src/store/utils.js b/packages/edit-widgets/src/store/utils.js index 381c577fc6627..a3d9452e2def5 100644 --- a/packages/edit-widgets/src/store/utils.js +++ b/packages/edit-widgets/src/store/utils.js @@ -12,6 +12,22 @@ export const KIND = 'root'; */ export const WIDGET_AREA_ENTITY_TYPE = 'widgetArea'; +/** + * "post type" of the widget area post. + * + * @type {string} + */ +export const POST_TYPE = 'postType'; + +/** + * Builds an ID for a new navigation post. + * + * @param {number} widgetAreaId Widget area id. + * @return {string} An ID. + */ +export const buildWidgetAreaPostId = ( widgetAreaId ) => + `widget-area-${ widgetAreaId }`; + /** * Builds a query to resolve sidebars. * From 997a7f557454b4b198e323aef453750b0c0ed085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 13:00:26 +0200 Subject: [PATCH 08/41] Display success/error notice after saving --- .../index.js | 10 ++--- packages/edit-widgets/src/store/actions.js | 41 ++++++++++++++++++- packages/edit-widgets/src/store/selectors.js | 19 +++------ 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js index 8aee4ea78b02d..38e5eaa6ad98f 100644 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js @@ -16,7 +16,7 @@ import { cog } from '@wordpress/icons'; */ import './sync-customizer'; import Header from '../header'; -import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; +// import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; function CustomizerEditWidgetsInitializer( { settings } ) { @@ -29,9 +29,9 @@ function CustomizerEditWidgetsInitializer( { settings } ) { [ settings ] ); return ( - + //
-
+ //
); } diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index 19ea67884a991..a36eb19874f98 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -3,6 +3,11 @@ */ import { invert } from 'lodash'; +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + /** * Internal dependencies */ @@ -24,13 +29,35 @@ export function* saveEditedWidgetAreas() { if ( ! editedWidgetAreas?.length ) { return; } + try { + yield* saveWidgetAreas( editedWidgetAreas ); + yield dispatch( + 'core/notices', + 'createSuccessNotice', + __( 'Widgets saved.' ), + { + type: 'snackbar', + } + ); + } catch ( e ) { + yield dispatch( + 'core/notices', + 'createErrorNotice', + __( 'There was an error.' ), + { + type: 'snackbar', + } + ); + } +} +export function* saveWidgetAreas( widgetAreas ) { const widgets = yield select( 'core/edit-widgets', 'getWidgets' ); const widgetIdToClientId = yield getWidgetToClientIdMapping(); const clientIdToWidgetId = invert( widgetIdToClientId ); - // @TODO: Batch save and concurrency - for ( const widgetArea of editedWidgetAreas ) { + // @TODO: Batch save / concurrency + for ( const widgetArea of widgetAreas ) { const post = yield select( 'core', 'getEditedEntityRecord', @@ -63,4 +90,14 @@ export function* saveEditedWidgetAreas() { widgetArea.id ); } + + // saveEditedEntityRecord resets the resolution status, let's fix it manually + yield dispatch( + 'core', + 'finishResolution', + 'getEntityRecord', + KIND, + WIDGET_AREA_ENTITY_TYPE, + buildWidgetAreasQuery() + ); } diff --git a/packages/edit-widgets/src/store/selectors.js b/packages/edit-widgets/src/store/selectors.js index 6b5b1f42531d3..38427217bda36 100644 --- a/packages/edit-widgets/src/store/selectors.js +++ b/packages/edit-widgets/src/store/selectors.js @@ -97,24 +97,17 @@ export const isSavingWidgetAreas = createRegistrySelector( */ export const hasResolvedWidgetAreas = createRegistrySelector( ( select, query = buildWidgetAreasQuery() ) => () => { - const resolutionFinished = select( - 'core' - ).hasFinishedResolution( 'getEntityRecords', [ - KIND, - WIDGET_AREA_ENTITY_TYPE, - query, - ] ); - if ( ! resolutionFinished ) { - return false; - } - const areas = select( 'core' ).getEntityRecords( KIND, WIDGET_AREA_ENTITY_TYPE, query ); - if ( ! areas.length ) { - return false; + if ( ! areas?.length ) { + return select( 'core' ).hasFinishedResolution( 'getEntityRecords', [ + KIND, + WIDGET_AREA_ENTITY_TYPE, + query, + ] ); } return true; From c234c194bd4ad6a525526a9c71f6014a93361946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 13:33:39 +0200 Subject: [PATCH 09/41] Add the experimental sidebars controller --- lib/class-wp-rest-sidebars-controller.php | 342 ++++++++++++++++++ lib/load.php | 3 + lib/rest-api.php | 9 + .../src/legacy-widget/edit/dom-manager.js | 10 +- packages/core-data/src/entities.js | 10 +- 5 files changed, 360 insertions(+), 14 deletions(-) create mode 100644 lib/class-wp-rest-sidebars-controller.php diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php new file mode 100644 index 0000000000000..595400b677609 --- /dev/null +++ b/lib/class-wp-rest-sidebars-controller.php @@ -0,0 +1,342 @@ +. + * + * @author Martin Pettersson + * @copyright 2015 Martin Pettersson + * @license GPLv2 + * @link https://github.com/martin-pettersson/wp-rest-api-sidebars + */ + +/** + * Class Sidebars_Controller + * + * @package WP_API_Sidebars\Controllers + */ +class WP_REST_Sidebars_Controller extends WP_REST_Controller { + + /** + * Plugins controller constructor. + * + * @since 5.5.0 + */ + public function __construct() { + $this->namespace = '__experimental'; + $this->rest_base = 'sidebars'; + } + + /** + * Registers the controllers routes + * + * @return null + */ + public function register_routes() { + // lists all sidebars + register_rest_route( + $this->namespace, + '/' . $this->rest_base, [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + ], + ] ); + + // lists a single sidebar based on the given id + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\w-]+)', + [ + [ + 'methods' => WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'args' => [ + 'id' => [ + 'description' => 'The id of a registered sidebar', + 'type' => 'string', + 'validate_callback' => function ( $id ) { + return ! is_null( self::get_sidebar( $id ) ); + }, + ], + ], + ], + ] + ); + + // lists a single sidebar based on the given id + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\w-]+)', + [ + [ + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'update_item' ], + 'args' => [ + 'id' => [ + 'description' => 'The id of a registered sidebar', + 'type' => 'string', + 'validate_callback' => function ( $id ) { + return ! is_null( self::get_sidebar( $id ) ); + }, + ], + ], + ], + ] + ); + } + + public function update_item( $request ) { + global $wp_registered_widget_updates; + $sidebar_id = $request->get_param( 'id' ); + $input_widgets = $request->get_param( 'widgets' ); + + if ( ! is_array( $input_widgets ) ) { + return; + } + + $numbers = []; + foreach ( $wp_registered_widget_updates as $id_base => $control ) { + $numbers[ $id_base ] = $control['callback'][0]->number + 1; + } + + $created_widgets = []; + + // Create and update widgets + $sidebar_widgets_ids = []; + foreach ( $input_widgets as $input_widget ) { + if ( ! isset( $wp_registered_widget_updates[ $input_widget['id_base'] ] ) ) { + continue; + } + $update_control = $wp_registered_widget_updates[ $input_widget['id_base'] ]; + if ( ! isset( $input_widget['id'] ) ) { + $number = $numbers[ $input_widget['id_base'] ] ++; + $id = $input_widget['id_base'] . '-' . $number; + + $input_widget['id'] = $id; + $input_widget['number'] = $number; + $created_widgets[] = [ + 'id' => $id, + 'number' => $number, + 'id_base' => $input_widget['id_base'], + ]; + } + + $_POST = $input_widget; + + $_POST[ 'widget-' . $input_widget['id_base'] ][ $input_widget['number'] ] = $input_widget['settings']; + call_user_func_array( $update_control['callback'], [] ); + $update_control['callback'][0]->updated = false; + + $sidebar_widgets_ids[] = $input_widget['id']; + } + + // Update sidebar to only have the widgets we just processed + $sidebars = wp_get_sidebars_widgets(); + $sidebars[ $sidebar_id ] = $sidebar_widgets_ids; + wp_set_sidebars_widgets( $sidebars ); + +// return new WP_REST_Response( [ +// 'success' => true, +// 'created_widgets' => $created_widgets, +// ], 200 ); + $request = new WP_REST_Request( 'GET' ); + $request->set_param( 'id', $sidebar_id ); + + return $this->get_item( $request ); + } + + /** + * Returns a list of sidebars (active or inactive) + * + * @param WP_REST_Request $request + * + * @return WP_REST_Response + * @global array $wp_registered_sidebars + * + */ + public function get_items( $request ) { + // do type checking here as the method declaration must be compatible with parent + if ( ! $request instanceof WP_REST_Request ) { + throw new InvalidArgumentException( __METHOD__ . ' expects an instance of WP_REST_Request' ); + } + + global $wp_registered_sidebars; + + $sidebars = []; + + foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) { + $sidebar = compact( 'id', 'widgets' ); + + if ( isset( $wp_registered_sidebars[ $id ] ) ) { + $registered_sidebar = $wp_registered_sidebars[ $id ]; + + $sidebar['status'] = 'active'; + $sidebar['name'] = $registered_sidebar['name']; + $sidebar['description'] = $registered_sidebar['description']; + } else { + $sidebar['status'] = 'inactive'; + } + + if ( 1 || $request->has_param( 'with-widgets' ) ) { + $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); + } + + $sidebars[] = $sidebar; + } + + return new WP_REST_Response( $sidebars, 200 ); + } + + /** + * Returns the given sidebar + * + * @param WP_REST_Request $request + * + * @return WP_REST_Response + */ + public function get_item( $request ) { + // do type checking here as the method declaration must be compatible with parent + if ( ! $request instanceof WP_REST_Request ) { + throw new InvalidArgumentException( __METHOD__ . ' expects an instance of WP_REST_Request' ); + } + + $sidebar = self::get_sidebar( $request->get_param( 'id' ) ); + + $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); + + ob_start(); + dynamic_sidebar( $request->get_param( 'id' ) ); + $sidebar['rendered'] = ob_get_clean(); + + return new WP_REST_Response( $sidebar, 200 ); + } + + /** + * Returns a sidebar for the given id or null if not found + * + * Note: The id can be either an index, the id or the name of a sidebar + * + * @param string $id + * + * @return array|null + * @global array $wp_registered_sidebars + * + */ + public static function get_sidebar( $id ) { + global $wp_registered_sidebars; + + if ( is_int( $id ) ) { + $id = 'sidebar-' . $id; + } else { + $id = sanitize_title( $id ); + + foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { + if ( sanitize_title( $sidebar['name'] ) == $id ) { + return $sidebar; + } + } + } + + foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { + if ( $key === $id ) { + return $sidebar; + } + } + + return null; + } + + /** + * Returns a list of widgets for the given sidebar id + * + * @param string $sidebar_id + * + * @return array + * @global array $wp_registered_widgets + * @global array $wp_registered_sidebars + * + */ + public static function get_widgets( $sidebar_id ) { + global $wp_registered_widgets, $wp_registered_sidebars; + + $widgets = []; + $sidebars_widgets = (array) wp_get_sidebars_widgets(); + + if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) && isset( $sidebars_widgets[ $sidebar_id ] ) ) { + foreach ( $sidebars_widgets[ $sidebar_id ] as $widget_id ) { + // just to be sure + if ( isset( $wp_registered_widgets[ $widget_id ] ) ) { + $widget = $wp_registered_widgets[ $widget_id ]; + + // get the widget output + if ( is_callable( $widget['callback'] ) ) { + // @note: everything up to ob_start is taken from the dynamic_sidebar function + $widget_parameters = array_merge( + [ + array_merge( $wp_registered_sidebars[ $sidebar_id ], [ + 'widget_id' => $widget_id, + 'widget_name' => $widget['name'], + ] ), + ], + (array) $widget['params'] + ); + + $classname = ''; + foreach ( (array) $widget['classname'] as $cn ) { + if ( is_string( $cn ) ) { + $classname .= '_' . $cn; + } elseif ( is_object( $cn ) ) { + $classname .= '_' . get_class( $cn ); + } + } + $classname = ltrim( $classname, '_' ); + $widget_parameters[0]['before_widget'] = sprintf( $widget_parameters[0]['before_widget'], $widget_id, $classname ); + + ob_start(); + + call_user_func_array( $widget['callback'], $widget_parameters ); + + $widget['rendered'] = ob_get_clean(); + } + + if ( isset( $widget['callback'][0] ) ) { + $instance = $widget['callback'][0]; + $widget['widget_class'] = get_class( $instance ); + $widget['settings'] = \Experimental_WP_Widget_Blocks_Manager::get_sidebar_widget_instance( + $wp_registered_sidebars[ $sidebar_id ], + $widget_id + ); + $widget['number'] = (int) $widget['params'][0]['number']; + $widget['id_base'] = $instance->id_base; + } + + unset( $widget['params'] ); + unset( $widget['callback'] ); + + $widgets[] = $widget; + } + } + } + + return $widgets; + } + +} + diff --git a/lib/load.php b/lib/load.php index 583d0a4b338f5..996257edd5ecb 100644 --- a/lib/load.php +++ b/lib/load.php @@ -60,6 +60,9 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_REST_Plugins_Controller' ) ) { require_once dirname( __FILE__ ) . '/class-wp-rest-plugins-controller.php'; } + if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) { + require_once dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php'; + } if ( ! class_exists( 'WP_Widget_Block' ) ) { require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; } diff --git a/lib/rest-api.php b/lib/rest-api.php index 10b7724caf7f4..264c817269b40 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -199,6 +199,15 @@ function gutenberg_register_plugins_endpoint() { } add_action( 'rest_api_init', 'gutenberg_register_plugins_endpoint' ); +/** + * Registers the Sidebars REST API routes. + */ +function gutenberg_register_sidebars_endpoint() { + $sidebars = new WP_REST_Sidebars_Controller(); + $sidebars->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_sidebars_endpoint' ); + /** * Hook in to the nav menu item post type and enable a post type rest endpoint. * diff --git a/packages/block-library/src/legacy-widget/edit/dom-manager.js b/packages/block-library/src/legacy-widget/edit/dom-manager.js index 80721ceb33e53..f2a2ff3cac9a0 100644 --- a/packages/block-library/src/legacy-widget/edit/dom-manager.js +++ b/packages/block-library/src/legacy-widget/edit/dom-manager.js @@ -71,11 +71,11 @@ class LegacyWidgetEditDomManager extends Component { if ( this.shouldTriggerInstanceUpdate() ) { if ( isReferenceWidget ) { if ( this.containerRef.current ) { - window.wpWidgets.save( - window.jQuery( - this.containerRef.current - ) - ); + // window.wpWidgets.save( + // window.jQuery( + // this.containerRef.current + // ) + // ); } } this.props.onInstanceChange( diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index a3a0631049145..b487b9c9ad093 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -54,18 +54,10 @@ export const defaultEntities = [ plural: 'taxonomies', label: __( 'Taxonomy' ), }, - // { - // name: 'widgetArea', - // kind: 'root', - // baseURL: '/__experimental/widget-areas', - // plural: 'widgetAreas', - // transientEdits: { blocks: true }, - // label: __( 'Widget area' ), - // }, { name: 'widgetArea', kind: 'root', - baseURL: '/wp-rest-api-sidebars/v1/sidebars', + baseURL: '/__experimental/sidebars', plural: 'widgetAreas', transientEdits: { blocks: true }, label: __( 'Widget areas' ), From 1267acc8b3e9dd48ff4b5648ecbc664d14117203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 13:46:45 +0200 Subject: [PATCH 10/41] Clean up unused apis --- ...-experimental-wp-widget-blocks-manager.php | 266 ----------------- lib/class-wp-rest-widget-areas-controller.php | 275 ------------------ lib/class-wp-rest-widget-forms.php | 186 ------------ lib/load.php | 6 +- lib/rest-api.php | 21 -- 5 files changed, 1 insertion(+), 753 deletions(-) delete mode 100644 lib/class-wp-rest-widget-areas-controller.php delete mode 100644 lib/class-wp-rest-widget-forms.php diff --git a/lib/class-experimental-wp-widget-blocks-manager.php b/lib/class-experimental-wp-widget-blocks-manager.php index db9337b54c7eb..ad73c9e29a908 100644 --- a/lib/class-experimental-wp-widget-blocks-manager.php +++ b/lib/class-experimental-wp-widget-blocks-manager.php @@ -58,107 +58,6 @@ public static function get_wp_registered_sidebars_sidebar( $sidebar_id ) { return $wp_registered_sidebars[ $sidebar_id ]; } - /** - * Returns the result of wp_get_sidebars_widgets without swap_out_sidebars_blocks_for_block_widgets filter being applied. - * - * @since 5.7.0 - */ - private static function get_raw_sidebar_widgets() { - return self::$unfiltered_sidebar_widgets; - } - - /** - * Returns a post id being referenced in a sidebar area. - * - * @since 5.7.0 - * - * @param string $sidebar_id Identifier of the sidebar. - * @return integer Post id. - */ - public static function get_post_id_referenced_in_sidebar( $sidebar_id ) { - $sidebars = self::get_raw_sidebar_widgets(); - $sidebar = $sidebars[ $sidebar_id ]; - return is_numeric( $sidebar ) ? $sidebar : 0; - } - - /** - * Updates a sidebar structure to reference a $post_id, and makes the widgets being referenced inactive. - * - * @since 5.7.0 - * - * @param string $sidebar_id Identifier of the sidebar. - * @param integer $post_id Post id. - */ - public static function reference_post_id_in_sidebar( $sidebar_id, $post_id ) { - $sidebars = self::get_raw_sidebar_widgets(); - $sidebar = $sidebars[ $sidebar_id ]; - wp_set_sidebars_widgets( - array_merge( - $sidebars, - array( - $sidebar_id => $post_id, - 'wp_inactive_widgets' => array_merge( - $sidebars['wp_inactive_widgets'], - $sidebar - ), - ) - ) - ); - } - - /** - * Returns a sidebar as an array of legacy widget blocks. - * - * @since 5.7.0 - * - * @param string $sidebar_id Identifier of the sidebar. - * @return array $post_id Post id. - */ - public static function get_sidebar_as_blocks( $sidebar_id ) { - $blocks = array(); - - $sidebars_items = self::get_raw_sidebar_widgets(); - $wp_registered_sidebars = self::get_wp_registered_sidebars(); - - foreach ( $sidebars_items[ $sidebar_id ] as $item ) { - $widget_class = self::get_widget_class( $item ); - list( $object, $number ) = self::get_widget_info( $item ); - $new_block = array( - 'blockName' => 'core/legacy-widget', - 'attrs' => array( - 'id' => $item, - 'instance' => self::get_sidebar_widget_instance( $wp_registered_sidebars[ $sidebar_id ], $item ), - ), - 'innerHTML' => '', - ); - if ( null !== $widget_class ) { - $new_block['attrs']['widgetClass'] = $widget_class; - } - if ( isset( $object->id_base ) ) { - $new_block['attrs']['idBase'] = $object->id_base; - } - if ( is_int( $number ) ) { - $new_block['attrs']['number'] = $number; - } - $blocks[] = $new_block; - } - return $blocks; - } - - /** - * Verifies if a sidebar id is valid or not. - * - * @since 5.7.0 - * - * @param string $sidebar_id Identifier of the sidebar. - * @return boolean True if the $sidebar_id value is valid and false otherwise. - */ - public static function is_valid_sidebar_id( $sidebar_id ) { - $wp_registered_sidebars = self::get_wp_registered_sidebars(); - return isset( $wp_registered_sidebars[ $sidebar_id ] ); - } - - /** * Given a widget id returns the name of the class the represents the widget. * @@ -252,169 +151,4 @@ private static function get_widget_info( $widget_id ) { return array( $object, $number, $name ); } - /** - * Serializes an array of blocks. - * - * @since 5.7.0 - * - * @param array $blocks Post Array of block objects. - * @return string String representing the blocks. - */ - public static function serialize_blocks( $blocks ) { - return implode( array_map( 'self::serialize_block', $blocks ) ); - } - - /** - * Serializes a block. - * - * @since 5.7.0 - * - * @param array $block Block object. - * @return string String representing the block. - */ - public static function serialize_block( $block ) { - if ( ! isset( $block['blockName'] ) ) { - return false; - } - $name = $block['blockName']; - if ( 0 === strpos( $name, 'core/' ) ) { - $name = substr( $name, strlen( 'core/' ) ); - } - - if ( empty( $block['attrs'] ) ) { - $opening_tag_suffix = ''; - } else { - $opening_tag_suffix = ' ' . json_encode( $block['attrs'] ); - } - - if ( empty( $block['innerHTML'] ) ) { - return sprintf( - '', - $name, - $opening_tag_suffix - ); - } else { - return sprintf( - '%3$s', - $name, - $opening_tag_suffix, - $block['innerHTML'] - ); - } - } - - /** - * Outputs a block widget on the website frontend. - * - * @param array $options Widget options. - * @param array $arguments Arguments array. - */ - public static function output_blocks_widget( $options, $arguments ) { - echo $options['before_widget']; - foreach ( $arguments['blocks'] as $block ) { - echo render_block( $block ); - } - echo $options['after_widget']; - } - - /** - * Noop block widget control output function for the necessary call to `wp_register_widget_control`. - */ - public static function output_blocks_widget_control() {} - - /** - * Registers a widget that should represent a set of blocks and returns its ID. - * - * @param array $blocks Array of blocks. - */ - public static function convert_blocks_to_widget( $blocks ) { - $widget_id = 'blocks-widget-' . md5( self::serialize_blocks( $blocks ) ); - global $wp_registered_widgets; - if ( isset( $wp_registered_widgets[ $widget_id ] ) ) { - return $widget_id; - } - wp_register_sidebar_widget( - $widget_id, - __( 'Blocks Area', 'gutenberg' ), - 'Experimental_WP_Widget_Blocks_Manager::output_blocks_widget', - array( - 'classname' => 'widget-area', - 'description' => __( 'Displays a set of blocks', 'gutenberg' ), - ), - array( - 'blocks' => $blocks, - ) - ); - wp_register_widget_control( - $widget_id, - __( 'Blocks Area', 'gutenberg' ), - 'Experimental_WP_Widget_Blocks_Manager::output_blocks_widget_control', - array( 'id_base' => 'blocks-widget' ) - ); - return $widget_id; - } - - /** - * Filters the $sidebars_widgets to exchange wp_area post id with a widget that renders that block area. - * - * @param array $sidebars_widgets_input An associative array of sidebars and their widgets. - */ - public static function swap_out_sidebars_blocks_for_block_widgets( $sidebars_widgets_input ) { - global $sidebars_widgets; - global $wp_customize; - if ( null === self::$unfiltered_sidebar_widgets ) { - self::$unfiltered_sidebar_widgets = $sidebars_widgets; - } - $changeset_data = null; - if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) { - $changeset_data = $wp_customize->changeset_data(); - if ( isset( $changeset_data['gutenberg_widget_blocks']['value'] ) ) { - $changeset_data = json_decode( $changeset_data['gutenberg_widget_blocks']['value'] ); - } - } - - $filtered_sidebar_widgets = array(); - foreach ( $sidebars_widgets_input as $sidebar_id => $item ) { - $changeset_value = $changeset_data && isset( $changeset_data->$sidebar_id ) - ? $changeset_data->$sidebar_id - : null; - - if ( ! is_numeric( $item ) && ! $changeset_value ) { - $filtered_sidebar_widgets[ $sidebar_id ] = $item; - continue; - } - - $filtered_widgets = array(); - $last_set_of_blocks = array(); - $blocks = parse_blocks( - $changeset_value ? $changeset_value : get_post( $item )->post_content - ); - - foreach ( $blocks as $block ) { - if ( ! isset( $block['blockName'] ) ) { - continue; - } - if ( - 'core/legacy-widget' === $block['blockName'] && - isset( $block['attrs']['identifier'] ) - ) { - if ( ! empty( $last_set_of_blocks ) ) { - $filtered_widgets[] = self::convert_blocks_to_widget( $last_set_of_blocks ); - $last_set_of_blocks = array(); - } - $filtered_widgets[] = $block['attrs']['identifier']; - } else { - $last_set_of_blocks[] = $block; - } - } - if ( ! empty( $last_set_of_blocks ) ) { - $filtered_widgets[] = self::convert_blocks_to_widget( $last_set_of_blocks ); - } - - $filtered_sidebar_widgets[ $sidebar_id ] = $filtered_widgets; - } - $sidebars_widgets = $filtered_sidebar_widgets; - - return $filtered_sidebar_widgets; - } } diff --git a/lib/class-wp-rest-widget-areas-controller.php b/lib/class-wp-rest-widget-areas-controller.php deleted file mode 100644 index d03bf04435ea7..0000000000000 --- a/lib/class-wp-rest-widget-areas-controller.php +++ /dev/null @@ -1,275 +0,0 @@ -namespace = '__experimental'; - $this->rest_base = 'widget-areas'; - } - - /** - * Registers the necessary REST API routes. - * - * @access public - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - - $id_argument = array( - 'description' => __( 'The sidebar’s ID.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - 'validate_callback' => 'Experimental_WP_Widget_Blocks_Manager::is_valid_sidebar_id', - ); - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P.+)', - array( - 'args' => array( - 'id' => $id_argument, - ), - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'permission_callback' => array( $this, 'update_item_permissions_check' ), - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } - - /** - * Retrieves the comment's schema, conforming to JSON Schema. - * - * @since 6.1.0 - * - * @return array - */ - public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'widget-area', - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the object.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'content' => array( - 'description' => __( 'The content for the object.', 'gutenberg' ), - 'type' => 'object', - 'context' => array( 'view', 'edit', 'embed' ), - 'arg_options' => array( - 'sanitize_callback' => null, - 'validate_callback' => null, - ), - 'properties' => array( - 'raw' => array( - 'description' => __( 'Content for the object, as it exists in the database.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'rendered' => array( - 'description' => __( 'HTML content for the object, transformed for display.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'block_version' => array( - 'description' => __( 'Version of the content block format used by the object.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - ), - ), - ), - ); - - return $schema; - } - - /** - * Checks whether a given request has permission to read widget areas. - * - * @since 5.7.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. - * - * This function is overloading a function defined in WP_REST_Controller so it should have the same parameters. - * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - */ - public function get_items_permissions_check( $request ) { - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_user_cannot_view', - __( 'Sorry, you are not allowed to read sidebars.', 'gutenberg' ) - ); - } - - return true; - } - /* phpcs:enable */ - - /** - * Retrieves all widget areas. - * - * @since 5.7.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - global $wp_registered_sidebars; - - $data = array(); - - foreach ( array_keys( $wp_registered_sidebars ) as $sidebar_id ) { - $data[ $sidebar_id ] = $this->get_sidebar_data( $sidebar_id ); - } - - return rest_ensure_response( $data ); - } - - /** - * Retrieves a specific widget area. - * - * @since 5.7.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. - */ - public function get_item( $request ) { - return rest_ensure_response( $this->get_sidebar_data( $request['id'] ) ); - } - - /** - * Checks if a given REST request has access to update a widget area. - * - * @since 5.7.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_Error|bool True if the request has access to update the item, error object otherwise. - * - * This function is overloading a function defined in WP_REST_Controller so it should have the same parameters. - * phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - */ - public function update_item_permissions_check( $request ) { - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'rest_user_cannot_edit', - __( 'Sorry, you are not allowed to edit sidebars.', 'gutenberg' ) - ); - } - - return true; - } - /* phpcs:enable */ - - /** - * Updates a single widget area. - * - * @since 5.7.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function update_item( $request ) { - $sidebar_id = $request->get_param( 'id' ); - $sidebar_content = $request->get_param( 'content' ); - - if ( ! is_string( $sidebar_content ) && isset( $sidebar_content['raw'] ) ) { - $sidebar_content = $sidebar_content['raw']; - } - - $id_referenced_in_sidebar = Experimental_WP_Widget_Blocks_Manager::get_post_id_referenced_in_sidebar( $sidebar_id ); - - $post_id = wp_insert_post( - array( - 'ID' => $id_referenced_in_sidebar, - 'post_content' => $sidebar_content, - 'post_type' => 'wp_area', - ) - ); - - if ( 0 === $id_referenced_in_sidebar ) { - Experimental_WP_Widget_Blocks_Manager::reference_post_id_in_sidebar( $sidebar_id, $post_id ); - } - - return rest_ensure_response( $this->get_sidebar_data( $request['id'] ) ); - } - - /** - * Returns the sidebar data together with a content array containing the blocks present in the sidebar. - * The bocks may be legacy widget blocks representing the widgets currently present in the sidebar, or the content of a wp_area post that the sidebar references. - * - * @since 5.7.0 - * - * @param string $sidebar_id Identifier of the sidebar. - * @return object Sidebar data with a content array. - */ - protected function get_sidebar_data( $sidebar_id ) { - $content_string = ''; - - $post_id_referenced_in_sidebar = Experimental_WP_Widget_Blocks_Manager::get_post_id_referenced_in_sidebar( $sidebar_id ); - - if ( 0 !== $post_id_referenced_in_sidebar ) { - $post = get_post( $post_id_referenced_in_sidebar ); - $content_string = $post->post_content; - } else { - $blocks = Experimental_WP_Widget_Blocks_Manager::get_sidebar_as_blocks( $sidebar_id ); - $content_string = Experimental_WP_Widget_Blocks_Manager::serialize_blocks( $blocks ); - } - - return array_merge( - Experimental_WP_Widget_Blocks_Manager::get_wp_registered_sidebars_sidebar( $sidebar_id ), - array( - 'content' => array( - 'raw' => $content_string, - 'rendered' => apply_filters( 'the_content', $content_string ), - 'block_version' => block_version( $content_string ), - ), - ) - ); - } -} diff --git a/lib/class-wp-rest-widget-forms.php b/lib/class-wp-rest-widget-forms.php deleted file mode 100644 index 6ff3d16c0b16f..0000000000000 --- a/lib/class-wp-rest-widget-forms.php +++ /dev/null @@ -1,186 +0,0 @@ -namespace = '__experimental'; - $this->rest_base = 'widget-forms'; - } - - /** - * Registers the necessary REST API route. - * - * @access public - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P[^/]*)/', - array( - 'args' => array( - 'widget_class' => array( - 'description' => __( 'Class name of the widget.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - 'validate_callback' => array( $this, 'is_valid_widget' ), - ), - 'instance' => array( - 'description' => __( 'Current widget instance', 'gutenberg' ), - 'type' => 'object', - 'default' => array(), - ), - 'instance_changes' => array( - 'description' => __( 'Array of instance changes', 'gutenberg' ), - 'type' => 'object', - 'default' => array(), - ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'permission_callback' => array( $this, 'compute_new_widget_permissions_check' ), - 'callback' => array( $this, 'compute_new_widget' ), - ), - ) - ); - } - - /** - * Checks if the user has permissions to make the request. - * - * @since 5.2.0 - * @access public - * - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function compute_new_widget_permissions_check() { - // Verify if the current user has edit_theme_options capability. - // This capability is required to access the widgets screen. - if ( ! current_user_can( 'edit_theme_options' ) ) { - return new WP_Error( - 'widgets_cannot_access', - __( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), - array( - 'status' => rest_authorization_required_code(), - ) - ); - } - return true; - } - - /** - * Checks if the widget being referenced is valid. - * - * @since 5.2.0 - * @param string $widget_class Name of the class the widget references. - * - * @return boolean| True if the widget being referenced exists and false otherwise. - */ - private function is_valid_widget( $widget_class ) { - $widget_class = urldecode( $widget_class ); - global $wp_widget_factory; - if ( ! $widget_class ) { - return false; - } - return isset( $wp_widget_factory->widgets[ $widget_class ] ) && - ( $wp_widget_factory->widgets[ $widget_class ] instanceof WP_Widget ); - } - - /** - * Returns the new widget instance and the form that represents it. - * - * @since 5.7.0 - * @access public - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function compute_new_widget( $request ) { - $widget_class = urldecode( $request->get_param( 'widget_class' ) ); - $instance = $request->get_param( 'instance' ); - $instance_changes = $request->get_param( 'instance_changes' ); - - global $wp_widget_factory; - $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; - - $widget_obj->_set( -1 ); - ob_start(); - - if ( ! empty( $instance_changes ) ) { - $old_instance = $instance; - $instance = $widget_obj->update( $instance_changes, $old_instance ); - - /** - * Filters a widget's settings before saving. - * - * Returning false will effectively short-circuit the widget's ability - * to update settings. The old setting will be returned. - * - * @since 5.2.0 - * - * @param array $instance The current widget instance's settings. - * @param array $instance_changes Array of new widget settings. - * @param array $old_instance Array of old widget settings. - * @param WP_Widget $widget_ob The widget instance. - */ - $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); - if ( false === $instance ) { - $instance = $old_instance; - } - } - - $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); - - $return = null; - if ( false !== $instance ) { - $return = $widget_obj->form( $instance ); - - /** - * Fires at the end of the widget control form. - * - * Use this hook to add extra fields to the widget form. The hook - * is only fired if the value passed to the 'widget_form_callback' - * hook is not false. - * - * Note: If the widget has no form, the text echoed from the default - * form method can be hidden using CSS. - * - * @since 5.2.0 - * - * @param WP_Widget $widget_obj The widget instance (passed by reference). - * @param null $return Return null if new fields are added. - * @param array $instance An array of the widget's settings. - */ - do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); - } - $form = ob_get_clean(); - - return rest_ensure_response( - array( - 'instance' => $instance, - 'form' => $form, - ) - ); - } -} -/** - * End: Include for phase 2 - */ diff --git a/lib/load.php b/lib/load.php index 996257edd5ecb..f9f12f4a0d36d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -29,12 +29,8 @@ function gutenberg_is_experiment_enabled( $name ) { /** * Start: Include for phase 2 */ - if ( ! class_exists( 'WP_REST_Widget_Forms' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-widget-forms.php'; - } - if ( ! class_exists( 'WP_REST_Widget_Areas_Controller' ) ) { + if ( ! class_exists( 'Experimental_WP_Widget_Blocks_Manager' ) ) { require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php'; - require dirname( __FILE__ ) . '/class-wp-rest-widget-areas-controller.php'; } if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php'; diff --git a/lib/rest-api.php b/lib/rest-api.php index 264c817269b40..1d24e26e56460 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -130,27 +130,6 @@ function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { /** * Start: Include for phase 2 */ -/** - * Registers the REST API routes needed by the legacy widget block. - * - * @since 5.0.0 - */ -function gutenberg_register_rest_widget_updater_routes() { - $widget_forms = new WP_REST_Widget_Forms(); - $widget_forms->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); - -/** - * Registers the widget area REST API routes. - * - * @since 5.7.0 - */ -function gutenberg_register_rest_widget_areas() { - $widget_areas_controller = new WP_REST_Widget_Areas_Controller(); - $widget_areas_controller->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_rest_widget_areas' ); /** * Registers the block directory. From 2d1d2d879e45518aa4c6d048e48381d8351ee20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 13:50:42 +0200 Subject: [PATCH 11/41] Restore the widget-forms endpoint --- lib/class-wp-rest-widget-forms.php | 191 +++++++++++++++++++++++++++++ lib/load.php | 3 + lib/rest-api.php | 10 ++ 3 files changed, 204 insertions(+) create mode 100644 lib/class-wp-rest-widget-forms.php diff --git a/lib/class-wp-rest-widget-forms.php b/lib/class-wp-rest-widget-forms.php new file mode 100644 index 0000000000000..bc14f36966be0 --- /dev/null +++ b/lib/class-wp-rest-widget-forms.php @@ -0,0 +1,191 @@ +namespace = '__experimental'; + $this->rest_base = 'widget-forms'; + } + + /** + * Registers the necessary REST API route. + * + * @access public + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[^/]*)/', + array( + 'args' => array( + 'widget_class' => array( + 'description' => __( 'Class name of the widget.', 'gutenberg' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'is_valid_widget' ), + ), + 'instance' => array( + 'description' => __( 'Current widget instance', 'gutenberg' ), + 'type' => 'object', + 'default' => array(), + ), + 'instance_changes' => array( + 'description' => __( 'Array of instance changes', 'gutenberg' ), + 'type' => 'object', + 'default' => array(), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'permission_callback' => array( $this, 'compute_new_widget_permissions_check' ), + 'callback' => array( $this, 'compute_new_widget' ), + ), + ) + ); + } + + /** + * Checks if the user has permissions to make the request. + * + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + * @since 5.2.0 + * @access public + * + */ + public function compute_new_widget_permissions_check() { + // Verify if the current user has edit_theme_options capability. + // This capability is required to access the widgets screen. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'widgets_cannot_access', + __( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + + return true; + } + + /** + * Checks if the widget being referenced is valid. + * + * @param string $widget_class Name of the class the widget references. + * + * @return boolean| True if the widget being referenced exists and false otherwise. + * @since 5.2.0 + */ + private function is_valid_widget( $widget_class ) { + $widget_class = urldecode( $widget_class ); + global $wp_widget_factory; + if ( ! $widget_class ) { + return false; + } + + return isset( $wp_widget_factory->widgets[ $widget_class ] ) && + ( $wp_widget_factory->widgets[ $widget_class ] instanceof WP_Widget ); + } + + /** + * Returns the new widget instance and the form that represents it. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * @since 5.7.0 + * @access public + * + */ + public function compute_new_widget( $request ) { + $widget_class = urldecode( $request->get_param( 'widget_class' ) ); + $instance = $request->get_param( 'instance' ); + $instance_changes = $request->get_param( 'instance_changes' ); + + global $wp_widget_factory; + $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; + + $widget_obj->_set( - 1 ); + ob_start(); + + if ( ! empty( $instance_changes ) ) { + $old_instance = $instance; + $instance = $widget_obj->update( $instance_changes, $old_instance ); + + /** + * Filters a widget's settings before saving. + * + * Returning false will effectively short-circuit the widget's ability + * to update settings. The old setting will be returned. + * + * @param array $instance The current widget instance's settings. + * @param array $instance_changes Array of new widget settings. + * @param array $old_instance Array of old widget settings. + * @param WP_Widget $widget_ob The widget instance. + * + * @since 5.2.0 + * + */ + $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); + if ( false === $instance ) { + $instance = $old_instance; + } + } + + $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); + + $return = null; + if ( false !== $instance ) { + $return = $widget_obj->form( $instance ); + + /** + * Fires at the end of the widget control form. + * + * Use this hook to add extra fields to the widget form. The hook + * is only fired if the value passed to the 'widget_form_callback' + * hook is not false. + * + * Note: If the widget has no form, the text echoed from the default + * form method can be hidden using CSS. + * + * @param WP_Widget $widget_obj The widget instance (passed by reference). + * @param null $return Return null if new fields are added. + * @param array $instance An array of the widget's settings. + * + * @since 5.2.0 + * + */ + do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); + } + $form = ob_get_clean(); + + return rest_ensure_response( + array( + 'instance' => $instance, + 'form' => $form, + ) + ); + } +} +/** + * End: Include for phase 2 + */ diff --git a/lib/load.php b/lib/load.php index f9f12f4a0d36d..81d2f66804639 100644 --- a/lib/load.php +++ b/lib/load.php @@ -29,6 +29,9 @@ function gutenberg_is_experiment_enabled( $name ) { /** * Start: Include for phase 2 */ + if ( ! class_exists( 'WP_REST_Widget_Forms' ) ) { + require dirname( __FILE__ ) . '/class-wp-rest-widget-forms.php'; + } if ( ! class_exists( 'Experimental_WP_Widget_Blocks_Manager' ) ) { require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php'; } diff --git a/lib/rest-api.php b/lib/rest-api.php index 1d24e26e56460..36a9ce7d1d71a 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -130,6 +130,16 @@ function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { /** * Start: Include for phase 2 */ +/** + * Registers the REST API routes needed by the legacy widget block. + * + * @since 5.0.0 + */ +function gutenberg_register_rest_widget_updater_routes() { + $widget_forms = new WP_REST_Widget_Forms(); + $widget_forms->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); /** * Registers the block directory. From 823d50dd4556180b2c1997494dc9c7e38348b23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 13:57:27 +0200 Subject: [PATCH 12/41] Refactor widget-forms to widget-utils controller --- ...class-wp-rest-widget-utils-controller.php} | 123 +++++++++++++++++- lib/load.php | 4 +- lib/rest-api.php | 2 +- .../src/legacy-widget/edit/handler.js | 2 +- 4 files changed, 120 insertions(+), 11 deletions(-) rename lib/{class-wp-rest-widget-forms.php => class-wp-rest-widget-utils-controller.php} (56%) diff --git a/lib/class-wp-rest-widget-forms.php b/lib/class-wp-rest-widget-utils-controller.php similarity index 56% rename from lib/class-wp-rest-widget-forms.php rename to lib/class-wp-rest-widget-utils-controller.php index bc14f36966be0..034d27099cd46 100644 --- a/lib/class-wp-rest-widget-forms.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -14,7 +14,7 @@ * * @see WP_REST_Controller */ -class WP_REST_Widget_Forms extends WP_REST_Controller { +class WP_REST_Widget_Utils_Controller extends WP_REST_Controller { /** * Constructs the controller. @@ -23,7 +23,7 @@ class WP_REST_Widget_Forms extends WP_REST_Controller { */ public function __construct() { $this->namespace = '__experimental'; - $this->rest_base = 'widget-forms'; + $this->rest_base = 'widget-utils'; } /** @@ -34,7 +34,7 @@ public function __construct() { public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P[^/]*)/', + '/' . $this->rest_base . '/form/(?P[^/]*)/', array( 'args' => array( 'widget_class' => array( @@ -56,8 +56,37 @@ public function register_routes() { ), array( 'methods' => WP_REST_Server::EDITABLE, - 'permission_callback' => array( $this, 'compute_new_widget_permissions_check' ), - 'callback' => array( $this, 'compute_new_widget' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'callback' => array( $this, 'compute_widget_form' ), + ), + ) + ); + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/preview/(?P[^/]*)/', + array( + 'args' => array( + 'widget_class' => array( + 'description' => __( 'Class name of the widget.', 'gutenberg' ), + 'type' => 'string', + 'required' => true, + 'validate_callback' => array( $this, 'is_valid_widget' ), + ), + 'instance' => array( + 'description' => __( 'Current widget instance', 'gutenberg' ), + 'type' => 'object', + 'default' => array(), + ), + 'instance_changes' => array( + 'description' => __( 'Array of instance changes', 'gutenberg' ), + 'type' => 'object', + 'default' => array(), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'permission_callback' => array( $this, 'permissions_check' ), + 'callback' => array( $this, 'compute_widget_preview' ), ), ) ); @@ -71,7 +100,7 @@ public function register_routes() { * @access public * */ - public function compute_new_widget_permissions_check() { + public function permissions_check() { // Verify if the current user has edit_theme_options capability. // This capability is required to access the widgets screen. if ( ! current_user_can( 'edit_theme_options' ) ) { @@ -116,7 +145,87 @@ private function is_valid_widget( $widget_class ) { * @access public * */ - public function compute_new_widget( $request ) { + public function compute_widget_form( $request ) { + $widget_class = urldecode( $request->get_param( 'widget_class' ) ); + $instance = $request->get_param( 'instance' ); + $instance_changes = $request->get_param( 'instance_changes' ); + + global $wp_widget_factory; + $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; + + $widget_obj->_set( - 1 ); + ob_start(); + + if ( ! empty( $instance_changes ) ) { + $old_instance = $instance; + $instance = $widget_obj->update( $instance_changes, $old_instance ); + + /** + * Filters a widget's settings before saving. + * + * Returning false will effectively short-circuit the widget's ability + * to update settings. The old setting will be returned. + * + * @param array $instance The current widget instance's settings. + * @param array $instance_changes Array of new widget settings. + * @param array $old_instance Array of old widget settings. + * @param WP_Widget $widget_ob The widget instance. + * + * @since 5.2.0 + * + */ + $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); + if ( false === $instance ) { + $instance = $old_instance; + } + } + + $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); + + $return = null; + if ( false !== $instance ) { + $return = $widget_obj->form( $instance ); + + /** + * Fires at the end of the widget control form. + * + * Use this hook to add extra fields to the widget form. The hook + * is only fired if the value passed to the 'widget_form_callback' + * hook is not false. + * + * Note: If the widget has no form, the text echoed from the default + * form method can be hidden using CSS. + * + * @param WP_Widget $widget_obj The widget instance (passed by reference). + * @param null $return Return null if new fields are added. + * @param array $instance An array of the widget's settings. + * + * @since 5.2.0 + * + */ + do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); + } + $form = ob_get_clean(); + + return rest_ensure_response( + array( + 'instance' => $instance, + 'form' => $form, + ) + ); + } + + /** + * Returns the new widget instance and the form that represents it. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + * @since 5.7.0 + * @access public + * + */ + public function compute_widget_preview( $request ) { $widget_class = urldecode( $request->get_param( 'widget_class' ) ); $instance = $request->get_param( 'instance' ); $instance_changes = $request->get_param( 'instance_changes' ); diff --git a/lib/load.php b/lib/load.php index 81d2f66804639..a1573212c006b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -29,8 +29,8 @@ function gutenberg_is_experiment_enabled( $name ) { /** * Start: Include for phase 2 */ - if ( ! class_exists( 'WP_REST_Widget_Forms' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-widget-forms.php'; + if ( ! class_exists( 'WP_REST_Widget_Utils_Controller' ) ) { + require dirname( __FILE__ ) . '/class-wp-rest-widget-utils-controller.php'; } if ( ! class_exists( 'Experimental_WP_Widget_Blocks_Manager' ) ) { require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php'; diff --git a/lib/rest-api.php b/lib/rest-api.php index 36a9ce7d1d71a..32ca31beaeefa 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -136,7 +136,7 @@ function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { * @since 5.0.0 */ function gutenberg_register_rest_widget_updater_routes() { - $widget_forms = new WP_REST_Widget_Forms(); + $widget_forms = new WP_REST_Widget_Utils_Controller(); $widget_forms->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); diff --git a/packages/block-library/src/legacy-widget/edit/handler.js b/packages/block-library/src/legacy-widget/edit/handler.js index 7ab37de2c344a..4bf5ecb96ffac 100644 --- a/packages/block-library/src/legacy-widget/edit/handler.js +++ b/packages/block-library/src/legacy-widget/edit/handler.js @@ -173,7 +173,7 @@ class LegacyWidgetEditHandler extends Component { if ( widgetClass ) { apiFetch( { - path: `/__experimental/widget-forms/${ encodeURIComponent( + path: `/__experimental/widget-utils/form/${ encodeURIComponent( widgetClass ) }/`, data: { From 2857bc74978cf3f61d43b49e942bcab42a10ee87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 15:29:00 +0200 Subject: [PATCH 13/41] Make widget form and preview work --- lib/class-wp-rest-widget-utils-controller.php | 103 ------------------ .../src/legacy-widget/edit/dom-manager.js | 51 +-------- .../src/legacy-widget/edit/handler.js | 31 ++---- .../src/legacy-widget/edit/index.js | 4 +- 4 files changed, 16 insertions(+), 173 deletions(-) diff --git a/lib/class-wp-rest-widget-utils-controller.php b/lib/class-wp-rest-widget-utils-controller.php index 034d27099cd46..ffbe61d89de62 100644 --- a/lib/class-wp-rest-widget-utils-controller.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -156,30 +156,6 @@ public function compute_widget_form( $request ) { $widget_obj->_set( - 1 ); ob_start(); - if ( ! empty( $instance_changes ) ) { - $old_instance = $instance; - $instance = $widget_obj->update( $instance_changes, $old_instance ); - - /** - * Filters a widget's settings before saving. - * - * Returning false will effectively short-circuit the widget's ability - * to update settings. The old setting will be returned. - * - * @param array $instance The current widget instance's settings. - * @param array $instance_changes Array of new widget settings. - * @param array $old_instance Array of old widget settings. - * @param WP_Widget $widget_ob The widget instance. - * - * @since 5.2.0 - * - */ - $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); - if ( false === $instance ) { - $instance = $old_instance; - } - } - $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); $return = null; @@ -215,85 +191,6 @@ public function compute_widget_form( $request ) { ); } - /** - * Returns the new widget instance and the form that represents it. - * - * @param WP_REST_Request $request Full details about the request. - * - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - * @since 5.7.0 - * @access public - * - */ - public function compute_widget_preview( $request ) { - $widget_class = urldecode( $request->get_param( 'widget_class' ) ); - $instance = $request->get_param( 'instance' ); - $instance_changes = $request->get_param( 'instance_changes' ); - - global $wp_widget_factory; - $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; - - $widget_obj->_set( - 1 ); - ob_start(); - - if ( ! empty( $instance_changes ) ) { - $old_instance = $instance; - $instance = $widget_obj->update( $instance_changes, $old_instance ); - - /** - * Filters a widget's settings before saving. - * - * Returning false will effectively short-circuit the widget's ability - * to update settings. The old setting will be returned. - * - * @param array $instance The current widget instance's settings. - * @param array $instance_changes Array of new widget settings. - * @param array $old_instance Array of old widget settings. - * @param WP_Widget $widget_ob The widget instance. - * - * @since 5.2.0 - * - */ - $instance = apply_filters( 'widget_update_callback', $instance, $instance_changes, $old_instance, $widget_obj ); - if ( false === $instance ) { - $instance = $old_instance; - } - } - - $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); - - $return = null; - if ( false !== $instance ) { - $return = $widget_obj->form( $instance ); - - /** - * Fires at the end of the widget control form. - * - * Use this hook to add extra fields to the widget form. The hook - * is only fired if the value passed to the 'widget_form_callback' - * hook is not false. - * - * Note: If the widget has no form, the text echoed from the default - * form method can be hidden using CSS. - * - * @param WP_Widget $widget_obj The widget instance (passed by reference). - * @param null $return Return null if new fields are added. - * @param array $instance An array of the widget's settings. - * - * @since 5.2.0 - * - */ - do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); - } - $form = ob_get_clean(); - - return rest_ensure_response( - array( - 'instance' => $instance, - 'form' => $form, - ) - ); - } } /** * End: Include for phase 2 diff --git a/packages/block-library/src/legacy-widget/edit/dom-manager.js b/packages/block-library/src/legacy-widget/edit/dom-manager.js index f2a2ff3cac9a0..ab3647f36230c 100644 --- a/packages/block-library/src/legacy-widget/edit/dom-manager.js +++ b/packages/block-library/src/legacy-widget/edit/dom-manager.js @@ -7,7 +7,6 @@ import { includes } from 'lodash'; * WordPress dependencies */ import { Component, createRef } from '@wordpress/element'; -import isShallowEqual from '@wordpress/is-shallow-equal'; class LegacyWidgetEditDomManager extends Component { constructor() { @@ -23,7 +22,6 @@ class LegacyWidgetEditDomManager extends Component { componentDidMount() { this.triggerWidgetEvent( 'widget-added' ); - this.previousFormData = new window.FormData( this.formRef.current ); } shouldComponentUpdate( nextProps ) { @@ -53,7 +51,6 @@ class LegacyWidgetEditDomManager extends Component { } if ( shouldTriggerWidgetUpdateEvent ) { this.triggerWidgetEvent( 'widget-updated' ); - this.previousFormData = new window.FormData( this.formRef.current ); } return false; } @@ -67,22 +64,9 @@ class LegacyWidgetEditDomManager extends Component { className="form" ref={ this.formRef } method="post" - onBlur={ () => { - if ( this.shouldTriggerInstanceUpdate() ) { - if ( isReferenceWidget ) { - if ( this.containerRef.current ) { - // window.wpWidgets.save( - // window.jQuery( - // this.containerRef.current - // ) - // ); - } - } - this.props.onInstanceChange( - this.retrieveUpdatedInstance() - ); - } - } } + onBlur={ () => + this.props.onInstanceChange( this.getFormData() ) + } >
{ + this.requestWidgetForm( undefined, ( response ) => { this.props.onInstanceChange( null, !! response.form ); } ); } @@ -41,11 +41,8 @@ class LegacyWidgetEditHandler extends Component { if ( ! this.widgetNonce ) { this.trySetNonce(); } - if ( - prevProps.instance !== this.props.instance && - this.instanceUpdating !== this.props.instance - ) { - this.requestWidgetUpdater( undefined, ( response ) => { + if ( prevProps.widgetClass !== this.props.widgetClass ) { + this.requestWidgetForm( undefined, ( response ) => { this.props.onInstanceChange( null, !! response.form ); } ); } @@ -126,20 +123,10 @@ class LegacyWidgetEditHandler extends Component { } onInstanceChange( instanceChanges ) { - const { id } = this.props; - if ( id ) { - // For reference widgets there is no need to query an endpoint, - // the widget is already saved with ajax. - this.props.onInstanceChange( instanceChanges, true ); - return; - } - this.requestWidgetUpdater( instanceChanges, ( response ) => { - this.instanceUpdating = response.instance; - this.props.onInstanceChange( response.instance, !! response.form ); - } ); + this.props.onInstanceChange( instanceChanges, true ); } - requestWidgetUpdater( instanceChanges, callback ) { + requestWidgetForm( updatedInstance, callback ) { const { id, idBase, instance, widgetClass } = this.props; const { isStillMounted } = this; if ( ! id && ! widgetClass ) { @@ -177,8 +164,10 @@ class LegacyWidgetEditHandler extends Component { widgetClass ) }/`, data: { - instance, - instance_changes: instanceChanges, + instance: { + ...instance, + ...updatedInstance, + }, }, method: 'POST', } ).then( ( response ) => { diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index f71ab5309e9fb..fa4eadfaa25e8 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { get } from 'lodash'; +import { get, omit } from 'lodash'; /** * WordPress dependencies @@ -174,7 +174,7 @@ class LegacyWidgetEdit extends Component { ); } From c38a77f70e01ac861ee8ff5a92bf12af31212027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 15:33:36 +0200 Subject: [PATCH 14/41] Remove $core_widgets = [] override --- lib/widgets.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/widgets.php b/lib/widgets.php index a8235252ba428..6bc481fe71cc5 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -129,7 +129,6 @@ function gutenberg_get_legacy_widget_settings() { 'WP_Nav_Menu_Widget', 'WP_Widget_Custom_HTML', ); - $core_widgets = array(); $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); $available_legacy_widgets = array(); From 362bc6ec4500bb0ae1aebc3fc73ad0ca0c65cf43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 15:37:03 +0200 Subject: [PATCH 15/41] Make the new widgets screen fully functional --- .../index.js | 58 ------ .../style.scss | 22 --- .../sync-customizer.js | 185 ------------------ packages/edit-widgets/src/index.js | 19 -- 4 files changed, 284 deletions(-) delete mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js delete mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss delete mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js deleted file mode 100644 index 38e5eaa6ad98f..0000000000000 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * WordPress dependencies - */ -import { navigateRegions } from '@wordpress/components'; -import { - __experimentalUseSimulatedMediaQuery as useSimulatedMediaQuery, - BlockInspector, -} from '@wordpress/block-editor'; -import { __ } from '@wordpress/i18n'; -import { useMemo } from '@wordpress/element'; -import { ComplementaryArea } from '@wordpress/interface'; -import { cog } from '@wordpress/icons'; - -/** - * Internal dependencies - */ -import './sync-customizer'; -import Header from '../header'; -// import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; -import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; - -function CustomizerEditWidgetsInitializer( { settings } ) { - useSimulatedMediaQuery( 'resizable-editor-section', 360 ); - const blockEditorSettings = useMemo( - () => ( { - ...settings, - hasFixedToolbar: true, - } ), - [ settings ] - ); - return ( - // -
-
- - - - - -
- //
- ); -} - -export default navigateRegions( CustomizerEditWidgetsInitializer ); diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss deleted file mode 100644 index 8bb5919d13cdb..0000000000000 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss +++ /dev/null @@ -1,22 +0,0 @@ -.edit-widgets-customizer-edit-widgets-initializer__content { - position: relative; - .edit-widgets-header { - margin-top: -16px; - border-top: 1px solid $gray-200; - } - - .edit-widgets-header, - .edit-widgets-header__block-toolbar { - background: #fff; - margin-left: -12px; - margin-right: -12px; - } - .edit-widgets-sidebar { - margin-left: -12px; - margin-right: -12px; - position: absolute; - top: 0; - width: calc(100% + 24px); - z-index: z-index(".edit-widgets-sidebar"); - } -} diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js deleted file mode 100644 index 6c26d5c7be5d2..0000000000000 --- a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * External dependencies - */ -import { throttle } from 'lodash'; - -/** - * WordPress dependencies - */ -import { parse, serialize } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { KIND, WIDGET_AREA_ENTITY_TYPE } from '../../store/utils'; - -/* -Widget area edits made in the Customizer are synced to Customizer -changesets as an object, encoded as a JSON string, where the keys -are widget area IDs and the values are serialized block content. -This file takes care of that syncing using the 2-way data binding -supported by `WP_Customize_Control`s. The process is as follows: - -- On load, the client checks if the current changeset has -widget areas that it can parse and use to hydrate the store. -It will load all widget areas for the current theme, but if -the changeset has content for a given area, it will replace -its actual published content with the changeset's. - -- On edit, the client updates the 2-way bound input with a new object that maps -widget area IDs and the values are serialized block content, encoded -as a JSON string. - -- On publish, a PHP action will parse the JSON string in the -changeset and update all the widget areas in it, to store the -new content. -*/ - -const waitForSelectValue = ( listener, value, changeTrigger ) => { - return new Promise( ( resolve ) => { - changeTrigger(); - if ( listener() === value ) { - resolve(); - return; - } - const unsubscribe = window.wp.data.subscribe( () => { - if ( listener() === value ) { - unsubscribe(); - resolve(); - } - } ); - } ); -}; - -// Get widget areas from the store in an `id => blocks` mapping. -const getWidgetAreasObject = () => { - const { getEditedEntityRecord } = window.wp.data.select( 'core' ); - const { getWidgetAreas } = window.wp.data.select( 'core/edit-widgets' ); - - return getWidgetAreas().reduce( ( widgetAreasObject, { id } ) => { - widgetAreasObject[ id ] = getEditedEntityRecord( - KIND, - WIDGET_AREA_ENTITY_TYPE, - id - ).blocks; - return widgetAreasObject; - }, {} ); -}; - -// Serialize the provided blocks and render them in the widget area with the provided ID. -const previewBlocksInWidgetArea = throttle( ( id, blocks ) => { - const customizePreviewIframe = document.querySelector( - '#customize-preview > iframe' - ); - if ( - ! customizePreviewIframe || - ! customizePreviewIframe.contentDocument - ) { - return; - } - - const widgetArea = customizePreviewIframe.contentDocument.querySelector( - `[data-customize-partial-placement-context*='"sidebar_id":"${ id }"']` - ); - if ( widgetArea ) { - widgetArea.innerHTML = serialize( blocks ); - widgetArea.parentElement.innerHTML = widgetArea.outerHTML; - } -}, 1000 ); - -// Update the hidden input that has 2-way data binding with Customizer settings. -const updateSettingInputValue = throttle( ( nextWidgetAreas ) => { - const settingInput = document.getElementById( - '_customize-input-gutenberg_widget_blocks' - ); - if ( settingInput ) { - settingInput.value = JSON.stringify( - Object.keys( nextWidgetAreas ).reduce( ( value, id ) => { - value[ id ] = serialize( nextWidgetAreas[ id ] ); - return value; - }, {} ) - ); - settingInput.dispatchEvent( new window.Event( 'change' ) ); - } -}, 1000 ); - -// Check that all the necessary globals are present. -if ( window.wp && window.wp.customize && window.wp.data ) { - let ran = false; - // Wait for the Customizer to finish bootstrapping. - window.wp.customize.bind( 'ready', () => - window.wp.customize.previewer.bind( 'ready', () => { - // The Customizer will call this on every preview refresh, - // but we only want to run it once to avoid running another - // store setup that would set changeset edits and save - // widget blocks unintentionally. - if ( ran ) { - return; - } - ran = true; - - // Try to parse a previous changeset from the hidden input. - let widgetAreas; - try { - widgetAreas = JSON.parse( - document.getElementById( - '_customize-input-gutenberg_widget_blocks' - ).value - ); - widgetAreas = Object.keys( widgetAreas ).reduce( - ( value, id ) => { - value[ id ] = parse( widgetAreas[ id ] ); - return value; - }, - {} - ); - } catch ( err ) { - widgetAreas = {}; - } - - // Wait for setup to finish before overwriting sidebars with changeset data, - // if any, and subscribe to registry changes after that so that we can preview - // changes and update the hidden input's value when any of the widget areas change. - waitForSelectValue( - () => - window.wp.data - .select( 'core/edit-widgets' ) - .hasResolvedWidgetAreas(), - true, - () => - window.wp.data - .select( 'core/edit-widgets' ) - .getWidgetAreas() - ).then( () => { - Object.keys( widgetAreas ).forEach( ( id ) => { - window.wp.data - .dispatch( 'core' ) - .editEntityRecord( KIND, WIDGET_AREA_ENTITY_TYPE, id, { - content: serialize( widgetAreas[ id ] ), - blocks: widgetAreas[ id ], - } ); - } ); - widgetAreas = getWidgetAreasObject(); - window.wp.data.subscribe( () => { - const nextWidgetAreas = getWidgetAreasObject(); - - let didUpdate = false; - for ( const id of Object.keys( nextWidgetAreas ) ) { - if ( widgetAreas[ id ] !== nextWidgetAreas[ id ] ) { - previewBlocksInWidgetArea( - id, - nextWidgetAreas[ id ] - ); - didUpdate = true; - } - } - - if ( didUpdate ) { - updateSettingInputValue( nextWidgetAreas ); - } - widgetAreas = nextWidgetAreas; - } ); - } ); - } ) - ); -} diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index ff9bc5d166bf3..2a7a08945288a 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -12,10 +12,8 @@ import { * Internal dependencies */ import './store'; -export { buildSidebarPostId } from './store/utils'; import './hooks'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; -import CustomizerEditWidgetsInitializer from './components/customizer-edit-widgets-initializer'; /** * Initializes the block editor in the widgets screen. @@ -33,20 +31,3 @@ export function initialize( id, settings ) { document.getElementById( id ) ); } - -/** - * Initializes the block editor in the widgets Customizer section. - * - * @param {string} id ID of the root element to render the section in. - * @param {Object} settings Block editor settings. - */ -export function customizerInitialize( id, settings ) { - registerCoreBlocks(); - if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks( settings ); - } - render( - , - document.getElementById( id ) - ); -} From 0c9ee1c86ce83b01ea23e705471517d06d6a6ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 15:39:04 +0200 Subject: [PATCH 16/41] Remove obsolete changes --- gutenberg.php | 63 --------------------------------------------------- 1 file changed, 63 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 7e412b491f60b..2a4f5355295ef 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -188,66 +188,3 @@ function gutenberg_register_widgets() { } add_action( 'widgets_init', 'gutenberg_register_widgets' ); - -function gutenberg_capture_menu_items( $menu_items, $args ) { - $args->menu_items = $menu_items; - return $menu_items; -} -add_filter( 'wp_nav_menu_objects', 'gutenberg_capture_menu_items', 10, 2 ); - -function gutenberg_convert_menu_item_to_block( $menu_item, &$menu_items_by_parent_id ) { - if ( 'html' === $menu_item->type ) { - return parse_blocks( $menu_item->content )[0]; - } - - $inner_blocks = array(); - - if ( isset( $menu_items_by_parent_id[ $menu_item->ID ] ) ) { - foreach ( $menu_items_by_parent_id[ $menu_item->ID ] as $child_menu_item ) { - $inner_blocks[] = gutenberg_convert_menu_item_to_block( - $child_menu_item, - $menu_items_by_parent_id - ); - } - } - - return array( - 'blockName' => 'core/navigation-link', - 'attrs' => array( - 'label' => $menu_item->title, - 'url' => $menu_item->url, - ), - 'innerBlocks' => $inner_blocks, - ); -} - -function gutenberg_handle_block_nav_menu( $nav_menu, $args ) { - if ( ! current_theme_supports( 'block-nav-menus' ) ) { - return $nav_menu; - } - - $menu_items_by_parent_id = array(); - foreach ( $args->menu_items as $menu_item ) { - $menu_items_by_parent_id[ intval( $menu_item->menu_item_parent ) ][] = $menu_item; - } - - $inner_blocks = array(); - - if ( isset( $menu_items_by_parent_id[0] ) ) { - foreach ( $menu_items_by_parent_id[0] as $menu_item ) { - $inner_blocks[] = gutenberg_convert_menu_item_to_block( - $menu_item, - $menu_items_by_parent_id - ); - } - } - - $navigation_block = array( - 'blockName' => 'core/navigation', - 'attrs' => array(), - 'innerBlocks' => $inner_blocks, - ); - - return render_block( $navigation_block ); -} -add_filter( 'wp_nav_menu', 'gutenberg_handle_block_nav_menu', 10, 2 ); From 84002c7ad56454b9fd015a586b996e11e90d1634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Wed, 5 Aug 2020 16:00:58 +0200 Subject: [PATCH 17/41] Remove unused code --- packages/edit-widgets/src/store/controls.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/edit-widgets/src/store/controls.js b/packages/edit-widgets/src/store/controls.js index d6bad2dcecd19..7998e814f8c32 100644 --- a/packages/edit-widgets/src/store/controls.js +++ b/packages/edit-widgets/src/store/controls.js @@ -1,7 +1,6 @@ /** * WordPress dependencies */ -import { default as triggerApiFetch } from '@wordpress/api-fetch'; import { createRegistryControl } from '@wordpress/data'; /** @@ -50,7 +49,7 @@ export function isProcessingPost( postId ) { } /** - * Selects widgetId -> clientId mapping (necessary for saving the navigation). + * Selects widgetId -> clientId mapping (necessary for saving widgets). * * @return {Object} Action. */ @@ -79,8 +78,7 @@ export function getNavigationPostForMenu( menuId ) { /** * Resolves menu items for given menu id. * - * @param {number} menuId Menu ID. - * @param query + * @param {Object} query Query. * @return {Object} Action. */ export function resolveWidgetAreas( query = buildWidgetAreasQuery() ) { @@ -125,10 +123,6 @@ export function dispatch( registryName, actionName, ...args ) { } const controls = { - API_FETCH( { request } ) { - return triggerApiFetch( request ); - }, - SELECT: createRegistryControl( ( registry ) => ( { registryName, selectorName, args } ) => { return registry.select( registryName )[ selectorName ]( ...args ); From 479b842a12e60a802b06e9d041e2d5341954530c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 12:37:45 +0200 Subject: [PATCH 18/41] Restore the experimental customizer UI --- .../index.js | 58 ++++++ .../style.scss | 22 +++ .../sync-customizer.js | 187 ++++++++++++++++++ .../src/components/layout/index.js | 48 ++--- .../index.js | 28 +++ packages/edit-widgets/src/index.js | 18 ++ 6 files changed, 331 insertions(+), 30 deletions(-) create mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js create mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss create mode 100644 packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js create mode 100644 packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js new file mode 100644 index 0000000000000..721f7e5b1dde6 --- /dev/null +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/index.js @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { navigateRegions } from '@wordpress/components'; +import { + __experimentalUseSimulatedMediaQuery as useSimulatedMediaQuery, + BlockInspector, +} from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { useMemo } from '@wordpress/element'; +import { ComplementaryArea } from '@wordpress/interface'; +import { cog } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import './sync-customizer'; +import Header from '../header'; +import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; +import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; + +function CustomizerEditWidgetsInitializer( { settings } ) { + useSimulatedMediaQuery( 'resizable-editor-section', 360 ); + const blockEditorSettings = useMemo( + () => ( { + ...settings, + hasFixedToolbar: true, + } ), + [ settings ] + ); + return ( + +
+
+ + + + + +
+
+ ); +} + +export default navigateRegions( CustomizerEditWidgetsInitializer ); diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss new file mode 100644 index 0000000000000..8bb5919d13cdb --- /dev/null +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/style.scss @@ -0,0 +1,22 @@ +.edit-widgets-customizer-edit-widgets-initializer__content { + position: relative; + .edit-widgets-header { + margin-top: -16px; + border-top: 1px solid $gray-200; + } + + .edit-widgets-header, + .edit-widgets-header__block-toolbar { + background: #fff; + margin-left: -12px; + margin-right: -12px; + } + .edit-widgets-sidebar { + margin-left: -12px; + margin-right: -12px; + position: absolute; + top: 0; + width: calc(100% + 24px); + z-index: z-index(".edit-widgets-sidebar"); + } +} diff --git a/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js new file mode 100644 index 0000000000000..4752d0305c0ba --- /dev/null +++ b/packages/edit-widgets/src/components/customizer-edit-widgets-initializer/sync-customizer.js @@ -0,0 +1,187 @@ +/** + * External dependencies + */ +import { throttle } from 'lodash'; + +/** + * WordPress dependencies + */ +import { parse, serialize } from '@wordpress/blocks'; + +/* +Widget area edits made in the Customizer are synced to Customizer +changesets as an object, encoded as a JSON string, where the keys +are widget area IDs and the values are serialized block content. +This file takes care of that syncing using the 2-way data binding +supported by `WP_Customize_Control`s. The process is as follows: + +- On load, the client checks if the current changeset has +widget areas that it can parse and use to hydrate the store. +It will load all widget areas for the current theme, but if +the changeset has content for a given area, it will replace +its actual published content with the changeset's. + +- On edit, the client updates the 2-way bound input with a new object that maps +widget area IDs and the values are serialized block content, encoded +as a JSON string. + +- On publish, a PHP action will parse the JSON string in the +changeset and update all the widget areas in it, to store the +new content. +*/ + +const waitForSelectValue = ( listener, value, changeTrigger ) => { + return new Promise( ( resolve ) => { + changeTrigger(); + if ( listener() === value ) { + resolve(); + return; + } + const unsubscribe = window.wp.data.subscribe( () => { + if ( listener() === value ) { + unsubscribe(); + resolve(); + } + } ); + } ); +}; + +// Get widget areas from the store in an `id => blocks` mapping. +const getWidgetAreasObject = () => { + const { getEntityRecords, getEditedEntityRecord } = window.wp.data.select( + 'core' + ); + + return getEntityRecords( 'root', 'widgetArea' ).reduce( + ( widgetAreasObject, { id } ) => { + widgetAreasObject[ id ] = getEditedEntityRecord( + 'root', + 'widgetArea', + id + ).blocks; + return widgetAreasObject; + }, + {} + ); +}; + +// Serialize the provided blocks and render them in the widget area with the provided ID. +const previewBlocksInWidgetArea = throttle( ( id, blocks ) => { + const customizePreviewIframe = document.querySelector( + '#customize-preview > iframe' + ); + if ( + ! customizePreviewIframe || + ! customizePreviewIframe.contentDocument + ) { + return; + } + + const widgetArea = customizePreviewIframe.contentDocument.querySelector( + `[data-customize-partial-placement-context*='"sidebar_id":"${ id }"']` + ); + if ( widgetArea ) { + widgetArea.innerHTML = serialize( blocks ); + widgetArea.parentElement.innerHTML = widgetArea.outerHTML; + } +}, 1000 ); + +// Update the hidden input that has 2-way data binding with Customizer settings. +const updateSettingInputValue = throttle( ( nextWidgetAreas ) => { + const settingInput = document.getElementById( + '_customize-input-gutenberg_widget_blocks' + ); + if ( settingInput ) { + settingInput.value = JSON.stringify( + Object.keys( nextWidgetAreas ).reduce( ( value, id ) => { + value[ id ] = serialize( nextWidgetAreas[ id ] ); + return value; + }, {} ) + ); + settingInput.dispatchEvent( new window.Event( 'change' ) ); + } +}, 1000 ); + +// Check that all the necessary globals are present. +if ( window.wp && window.wp.customize && window.wp.data ) { + let ran = false; + // Wait for the Customizer to finish bootstrapping. + window.wp.customize.bind( 'ready', () => + window.wp.customize.previewer.bind( 'ready', () => { + // The Customizer will call this on every preview refresh, + // but we only want to run it once to avoid running another + // store setup that would set changeset edits and save + // widget blocks unintentionally. + if ( ran ) { + return; + } + ran = true; + + // Try to parse a previous changeset from the hidden input. + let widgetAreas; + try { + widgetAreas = JSON.parse( + document.getElementById( + '_customize-input-gutenberg_widget_blocks' + ).value + ); + widgetAreas = Object.keys( widgetAreas ).reduce( + ( value, id ) => { + value[ id ] = parse( widgetAreas[ id ] ); + return value; + }, + {} + ); + } catch ( err ) { + widgetAreas = {}; + } + + // Wait for setup to finish before overwriting sidebars with changeset data, + // if any, and subscribe to registry changes after that so that we can preview + // changes and update the hidden input's value when any of the widget areas change. + waitForSelectValue( + () => + window.wp.data + .select( 'core' ) + .hasFinishedResolution( 'getEntityRecords', [ + 'root', + 'widgetArea', + ] ), + true, + () => + window.wp.data + .select( 'core' ) + .getEntityRecords( 'root', 'widgetArea' ) + ).then( () => { + Object.keys( widgetAreas ).forEach( ( id ) => { + window.wp.data + .dispatch( 'core' ) + .editEntityRecord( 'root', 'widgetArea', id, { + content: serialize( widgetAreas[ id ] ), + blocks: widgetAreas[ id ], + } ); + } ); + widgetAreas = getWidgetAreasObject(); + window.wp.data.subscribe( () => { + const nextWidgetAreas = getWidgetAreasObject(); + + let didUpdate = false; + for ( const id of Object.keys( nextWidgetAreas ) ) { + if ( widgetAreas[ id ] !== nextWidgetAreas[ id ] ) { + previewBlocksInWidgetArea( + id, + nextWidgetAreas[ id ] + ); + didUpdate = true; + } + } + + if ( didUpdate ) { + updateSettingInputValue( nextWidgetAreas ); + } + widgetAreas = nextWidgetAreas; + } ); + } ); + } ) + ); +} diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index fd68665a05470..7684ad7c828e9 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -1,20 +1,14 @@ /** * WordPress dependencies */ -import { - DropZoneProvider, - SlotFillProvider, - FocusReturnProvider, - Popover, -} from '@wordpress/components'; +import { Popover } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; -import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; import { InterfaceSkeleton, ComplementaryArea } from '@wordpress/interface'; /** * Internal dependencies */ -import KeyboardShortcuts from '../keyboard-shortcuts'; +import WidgetAreasBlockEditorProvider from '../widget-areas-block-editor-provider'; import Header from '../header'; import Sidebar from '../sidebar'; import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; @@ -27,29 +21,23 @@ function Layout( { blockEditorSettings } ) { } ); return ( <> - - - - - - } - sidebar={ - hasSidebarEnabled && ( - - ) - } - content={ - - } + + } + sidebar={ + hasSidebarEnabled && ( + + ) + } + content={ + - - - - - + } + /> + + + ); } diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js new file mode 100644 index 0000000000000..7ed9f567cc35b --- /dev/null +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -0,0 +1,28 @@ +/** + * WordPress dependencies + */ +import { + DropZoneProvider, + SlotFillProvider, + FocusReturnProvider, +} from '@wordpress/components'; +import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import KeyboardShortcuts from '../keyboard-shortcuts'; + +export default function WidgetAreasBlockEditorProvider( { children } ) { + return ( + <> + + + + + { children } + + + + ); +} diff --git a/packages/edit-widgets/src/index.js b/packages/edit-widgets/src/index.js index 2a7a08945288a..4e5adccb4db2a 100644 --- a/packages/edit-widgets/src/index.js +++ b/packages/edit-widgets/src/index.js @@ -14,6 +14,7 @@ import { import './store'; import './hooks'; import EditWidgetsInitializer from './components/edit-widgets-initializer'; +import CustomizerEditWidgetsInitializer from './components/customizer-edit-widgets-initializer'; /** * Initializes the block editor in the widgets screen. @@ -31,3 +32,20 @@ export function initialize( id, settings ) { document.getElementById( id ) ); } + +/** + * Initializes the block editor in the widgets Customizer section. + * + * @param {string} id ID of the root element to render the section in. + * @param {Object} settings Block editor settings. + */ +export function customizerInitialize( id, settings ) { + registerCoreBlocks(); + if ( process.env.GUTENBERG_PHASE === 2 ) { + __experimentalRegisterExperimentalCoreBlocks( settings ); + } + render( + , + document.getElementById( id ) + ); +} From 53294063b6b5bae3c5e21ba9a76187e88dc9eac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 12:52:13 +0200 Subject: [PATCH 19/41] Lint PHP code --- lib/class-wp-rest-sidebars-controller.php | 152 +++++++++--------- lib/class-wp-rest-widget-utils-controller.php | 6 +- lib/class-wp-widget-block.php | 8 +- 3 files changed, 79 insertions(+), 87 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 595400b677609..28df0d1fcc40c 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -1,5 +1,4 @@ namespace, - '/' . $this->rest_base, [ - [ - 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_items' ], - ], - ] ); - - // lists a single sidebar based on the given id + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + ), + ) + ); + + // Lists a single sidebar based on the given id. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', - [ - [ + array( + array( 'methods' => WP_REST_Server::READABLE, - 'callback' => [ $this, 'get_item' ], - 'args' => [ - 'id' => [ - 'description' => 'The id of a registered sidebar', + 'callback' => array( $this, 'get_item' ), + 'args' => array( + 'id' => array( + 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), 'type' => 'string', 'validate_callback' => function ( $id ) { return ! is_null( self::get_sidebar( $id ) ); }, - ], - ], - ], - ] + ), + ), + ), + ) ); - // lists a single sidebar based on the given id + // Update a single sidebar based on the given id. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', - [ - [ + array( + array( 'methods' => WP_REST_Server::EDITABLE, - 'callback' => [ $this, 'update_item' ], - 'args' => [ - 'id' => [ - 'description' => 'The id of a registered sidebar', + 'callback' => array( $this, 'update_item' ), + 'args' => array( + 'id' => array( + 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), 'type' => 'string', 'validate_callback' => function ( $id ) { return ! is_null( self::get_sidebar( $id ) ); }, - ], - ], - ], - ] + ), + 'widgets' => array( + 'description' => __( 'The list of widgets to save', 'gutenberg' ), + 'type' => 'array', + ), + ), + ), + ) ); } + /** + * Upodates af sidebars. + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response + * @global array $wp_registered_widget_updates + */ public function update_item( $request ) { global $wp_registered_widget_updates; $sidebar_id = $request->get_param( 'id' ); $input_widgets = $request->get_param( 'widgets' ); - if ( ! is_array( $input_widgets ) ) { - return; - } - - $numbers = []; + $numbers = array(); foreach ( $wp_registered_widget_updates as $id_base => $control ) { $numbers[ $id_base ] = $control['callback'][0]->number + 1; } - $created_widgets = []; + $created_widgets = array(); - // Create and update widgets - $sidebar_widgets_ids = []; + // Create and update widgets. + $sidebar_widgets_ids = array(); foreach ( $input_widgets as $input_widget ) { if ( ! isset( $wp_registered_widget_updates[ $input_widget['id_base'] ] ) ) { continue; @@ -131,31 +141,27 @@ public function update_item( $request ) { $input_widget['id'] = $id; $input_widget['number'] = $number; - $created_widgets[] = [ + $created_widgets[] = array( 'id' => $id, 'number' => $number, 'id_base' => $input_widget['id_base'], - ]; + ); } $_POST = $input_widget; $_POST[ 'widget-' . $input_widget['id_base'] ][ $input_widget['number'] ] = $input_widget['settings']; - call_user_func_array( $update_control['callback'], [] ); + call_user_func_array( $update_control['callback'], array() ); $update_control['callback'][0]->updated = false; $sidebar_widgets_ids[] = $input_widget['id']; } - // Update sidebar to only have the widgets we just processed + // Update sidebar to only have the widgets we just processed. $sidebars = wp_get_sidebars_widgets(); $sidebars[ $sidebar_id ] = $sidebar_widgets_ids; wp_set_sidebars_widgets( $sidebars ); -// return new WP_REST_Response( [ -// 'success' => true, -// 'created_widgets' => $created_widgets, -// ], 200 ); $request = new WP_REST_Request( 'GET' ); $request->set_param( 'id', $sidebar_id ); @@ -165,21 +171,15 @@ public function update_item( $request ) { /** * Returns a list of sidebars (active or inactive) * - * @param WP_REST_Request $request + * @param WP_REST_Request $request The request instance. * * @return WP_REST_Response * @global array $wp_registered_sidebars - * */ public function get_items( $request ) { - // do type checking here as the method declaration must be compatible with parent - if ( ! $request instanceof WP_REST_Request ) { - throw new InvalidArgumentException( __METHOD__ . ' expects an instance of WP_REST_Request' ); - } - global $wp_registered_sidebars; - $sidebars = []; + $sidebars = array(); foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) { $sidebar = compact( 'id', 'widgets' ); @@ -207,16 +207,11 @@ public function get_items( $request ) { /** * Returns the given sidebar * - * @param WP_REST_Request $request + * @param WP_REST_Request $request The request instance. * * @return WP_REST_Response */ public function get_item( $request ) { - // do type checking here as the method declaration must be compatible with parent - if ( ! $request instanceof WP_REST_Request ) { - throw new InvalidArgumentException( __METHOD__ . ' expects an instance of WP_REST_Request' ); - } - $sidebar = self::get_sidebar( $request->get_param( 'id' ) ); $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); @@ -233,11 +228,10 @@ public function get_item( $request ) { * * Note: The id can be either an index, the id or the name of a sidebar * - * @param string $id + * @param string $id ID of the sidebar. * * @return array|null * @global array $wp_registered_sidebars - * */ public static function get_sidebar( $id ) { global $wp_registered_sidebars; @@ -248,7 +242,7 @@ public static function get_sidebar( $id ) { $id = sanitize_title( $id ); foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { - if ( sanitize_title( $sidebar['name'] ) == $id ) { + if ( sanitize_title( $sidebar['name'] ) === $id ) { return $sidebar; } } @@ -266,35 +260,37 @@ public static function get_sidebar( $id ) { /** * Returns a list of widgets for the given sidebar id * - * @param string $sidebar_id + * @param string $sidebar_id ID of the sidebar. * * @return array * @global array $wp_registered_widgets * @global array $wp_registered_sidebars - * */ public static function get_widgets( $sidebar_id ) { global $wp_registered_widgets, $wp_registered_sidebars; - $widgets = []; + $widgets = array(); $sidebars_widgets = (array) wp_get_sidebars_widgets(); if ( isset( $wp_registered_sidebars[ $sidebar_id ] ) && isset( $sidebars_widgets[ $sidebar_id ] ) ) { foreach ( $sidebars_widgets[ $sidebar_id ] as $widget_id ) { - // just to be sure + // Just to be sure. if ( isset( $wp_registered_widgets[ $widget_id ] ) ) { $widget = $wp_registered_widgets[ $widget_id ]; - // get the widget output + // Get the widget output. if ( is_callable( $widget['callback'] ) ) { - // @note: everything up to ob_start is taken from the dynamic_sidebar function + // @note: everything up to ob_start is taken from the dynamic_sidebar function. $widget_parameters = array_merge( - [ - array_merge( $wp_registered_sidebars[ $sidebar_id ], [ - 'widget_id' => $widget_id, - 'widget_name' => $widget['name'], - ] ), - ], + array( + array_merge( + $wp_registered_sidebars[ $sidebar_id ], + array( + 'widget_id' => $widget_id, + 'widget_name' => $widget['name'], + ) + ), + ), (array) $widget['params'] ); diff --git a/lib/class-wp-rest-widget-utils-controller.php b/lib/class-wp-rest-widget-utils-controller.php index ffbe61d89de62..c9cef1920fa26 100644 --- a/lib/class-wp-rest-widget-utils-controller.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -98,7 +98,6 @@ public function register_routes() { * @return true|WP_Error True if the request has read access, WP_Error object otherwise. * @since 5.2.0 * @access public - * */ public function permissions_check() { // Verify if the current user has edit_theme_options capability. @@ -132,7 +131,7 @@ private function is_valid_widget( $widget_class ) { } return isset( $wp_widget_factory->widgets[ $widget_class ] ) && - ( $wp_widget_factory->widgets[ $widget_class ] instanceof WP_Widget ); + ( $wp_widget_factory->widgets[ $widget_class ] instanceof WP_Widget ); } /** @@ -143,12 +142,10 @@ private function is_valid_widget( $widget_class ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. * @since 5.7.0 * @access public - * */ public function compute_widget_form( $request ) { $widget_class = urldecode( $request->get_param( 'widget_class' ) ); $instance = $request->get_param( 'instance' ); - $instance_changes = $request->get_param( 'instance_changes' ); global $wp_widget_factory; $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; @@ -177,7 +174,6 @@ public function compute_widget_form( $request ) { * @param array $instance An array of the widget's settings. * * @since 5.2.0 - * */ do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); } diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index 4dd3eb226b657..18e4185fc1297 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -53,7 +53,7 @@ public function __construct() { * @param array $instance Settings for the current Block widget instance. */ public function widget( $args, $instance ) { - echo do_blocks( $instance[ 'content' ] ); + echo do_blocks( $instance['content'] ); } /** @@ -67,8 +67,8 @@ public function widget( $args, $instance ) { * @return array Settings to save or bool false to cancel saving. */ public function update( $new_instance, $old_instance ) { - $instance = array_merge( $this->default_instance, $old_instance ); - $instance['title'] = sanitize_text_field( $new_instance['title'] ); + $instance = array_merge( $this->default_instance, $old_instance ); + $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['content'] = $new_instance['content']; return $instance; } @@ -82,7 +82,7 @@ public function update( $new_instance, $old_instance ) { */ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, $this->default_instance ); - echo do_blocks( $instance[ 'content' ] ); + echo do_blocks( $instance['content'] ); ?> From 99009839df6ec12b183f33259db2ed2fba7359e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 13:19:02 +0200 Subject: [PATCH 20/41] Lint --- gutenberg.php | 4 +++- lib/class-wp-rest-widget-utils-controller.php | 4 ++-- lib/class-wp-widget-block.php | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 2a4f5355295ef..5dba248e1eb21 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -182,7 +182,9 @@ function register_site_icon_url( $response ) { add_filter( 'rest_index', 'register_site_icon_url' ); - +/** + * Registers the WP_Widget_Block widget + */ function gutenberg_register_widgets() { register_widget( 'WP_Widget_Block' ); } diff --git a/lib/class-wp-rest-widget-utils-controller.php b/lib/class-wp-rest-widget-utils-controller.php index c9cef1920fa26..1d46572103519 100644 --- a/lib/class-wp-rest-widget-utils-controller.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -144,8 +144,8 @@ private function is_valid_widget( $widget_class ) { * @access public */ public function compute_widget_form( $request ) { - $widget_class = urldecode( $request->get_param( 'widget_class' ) ); - $instance = $request->get_param( 'instance' ); + $widget_class = urldecode( $request->get_param( 'widget_class' ) ); + $instance = $request->get_param( 'instance' ); global $wp_widget_factory; $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index 18e4185fc1297..350f479cd0a5a 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -31,14 +31,14 @@ class WP_Widget_Block extends WP_Widget { public function __construct() { $widget_ops = array( 'classname' => 'widget_block', - 'description' => __( 'Gutenberg block.' ), + 'description' => __( 'Gutenberg block.', 'gutenberg' ), 'customize_selective_refresh' => true, ); $control_ops = array( 'width' => 400, 'height' => 350, ); - parent::__construct( 'block', __( 'Gutenberg Block' ), $widget_ops, $control_ops ); + parent::__construct( 'block', __( 'Gutenberg Block', 'gutenberg' ), $widget_ops, $control_ops ); } /** From 14cd40b99cc0ec9669b76a908dfaa524b04541dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 13:19:10 +0200 Subject: [PATCH 21/41] Add permissions_check --- lib/class-wp-rest-sidebars-controller.php | 40 +++++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 28df0d1fcc40c..30ec68da63f44 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -67,9 +67,10 @@ public function register_routes() { '/' . $this->rest_base . '/(?P[\w-]+)', array( array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_item' ), - 'args' => array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'args' => array( 'id' => array( 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), 'type' => 'string', @@ -88,9 +89,10 @@ public function register_routes() { '/' . $this->rest_base . '/(?P[\w-]+)', array( array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'update_item' ), - 'args' => array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'permissions_check' ), + 'args' => array( 'id' => array( 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), 'type' => 'string', @@ -109,7 +111,31 @@ public function register_routes() { } /** - * Upodates af sidebars. + * Checks if the user has permissions to make the request. + * + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + * @since 5.6.0 + * @access public + */ + public function permissions_check() { + // Verify if the current user has edit_theme_options capability. + // This capability is required to access the widgets screen. + if ( ! current_user_can( 'edit_theme_options' ) ) { + return new WP_Error( + 'widgets_cannot_access', + __( 'Sorry, you are not allowed to access widgets on this site.', 'gutenberg' ), + array( + 'status' => rest_authorization_required_code(), + ) + ); + } + + return true; + } + + + /** + * Updates the sidebar. * * @param WP_REST_Request $request The request instance. * From e1a145a82449b825514b750e7ec98a1343ccfc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 13:23:22 +0200 Subject: [PATCH 22/41] Define read / edit routes using a single declaration --- lib/class-wp-rest-sidebars-controller.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 30ec68da63f44..4863a04fe2a23 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -61,7 +61,7 @@ public function register_routes() { ) ); - // Lists a single sidebar based on the given id. + // Lists/updates a single sidebar based on the given id. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\w-]+)', @@ -80,14 +80,6 @@ public function register_routes() { ), ), ), - ) - ); - - // Update a single sidebar based on the given id. - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P[\w-]+)', - array( array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), From 6234176aa1971858df09076659c0b5bf3d90f9bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 13:24:37 +0200 Subject: [PATCH 23/41] replace get_param with array syntax --- lib/class-wp-rest-sidebars-controller.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 4863a04fe2a23..856886bd1cc20 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -136,8 +136,8 @@ public function permissions_check() { */ public function update_item( $request ) { global $wp_registered_widget_updates; - $sidebar_id = $request->get_param( 'id' ); - $input_widgets = $request->get_param( 'widgets' ); + $sidebar_id = $request['id']; + $input_widgets = $request['widgets']; $numbers = array(); foreach ( $wp_registered_widget_updates as $id_base => $control ) { @@ -230,12 +230,12 @@ public function get_items( $request ) { * @return WP_REST_Response */ public function get_item( $request ) { - $sidebar = self::get_sidebar( $request->get_param( 'id' ) ); + $sidebar = self::get_sidebar( $request['id'] ); $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); ob_start(); - dynamic_sidebar( $request->get_param( 'id' ) ); + dynamic_sidebar( $request['id'] ); $sidebar['rendered'] = ob_get_clean(); return new WP_REST_Response( $sidebar, 200 ); From 9fdc8cecaa8aa242771ea9dc7bbfbd6bb96d19dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 14:07:26 +0200 Subject: [PATCH 24/41] Add wp_slash to input widget settings --- lib/class-wp-rest-sidebars-controller.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 856886bd1cc20..2fcd6d5dcba42 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -166,9 +166,11 @@ public function update_item( $request ) { ); } - $_POST = $input_widget; + $field = 'widget-' . $input_widget['id_base']; + $number = $input_widget['number']; + $_POST = $input_widget; - $_POST[ 'widget-' . $input_widget['id_base'] ][ $input_widget['number'] ] = $input_widget['settings']; + $_POST[ $field ][ $number ] = wp_slash( $input_widget['settings'] ); call_user_func_array( $update_control['callback'], array() ); $update_control['callback'][0]->updated = false; From 102e09c003411de5598f961a7041fb6182d706bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 14:44:39 +0200 Subject: [PATCH 25/41] Use schema --- lib/class-wp-rest-sidebars-controller.php | 170 ++++++++++++++++++++-- 1 file changed, 159 insertions(+), 11 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 2fcd6d5dcba42..501ec8b2e5991 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -198,9 +198,9 @@ public function update_item( $request ) { */ public function get_items( $request ) { global $wp_registered_sidebars; + $fields = $this->get_fields_for_response( $request ); - $sidebars = array(); - + $data = array(); foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) { $sidebar = compact( 'id', 'widgets' ); @@ -214,14 +214,14 @@ public function get_items( $request ) { $sidebar['status'] = 'inactive'; } - if ( 1 || $request->has_param( 'with-widgets' ) ) { + if ( rest_is_field_included( 'widgets', $fields ) ) { $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); } - $sidebars[] = $sidebar; + $data[] = $this->prepare_item_for_response( $sidebar, $request )->get_data(); } - return new WP_REST_Response( $sidebars, 200 ); + return rest_ensure_response( $data ); } /** @@ -232,15 +232,14 @@ public function get_items( $request ) { * @return WP_REST_Response */ public function get_item( $request ) { + $fields = $this->get_fields_for_response( $request ); $sidebar = self::get_sidebar( $request['id'] ); - $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); - - ob_start(); - dynamic_sidebar( $request['id'] ); - $sidebar['rendered'] = ob_get_clean(); + if ( rest_is_field_included( 'widgets', $fields ) ) { + $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); + } - return new WP_REST_Response( $sidebar, 200 ); + return $this->prepare_item_for_response( $sidebar, $request ); } /** @@ -354,5 +353,154 @@ public static function get_widgets( $sidebar_id ) { return $widgets; } + /** + * Prepare a single sidebar output for response + * + * @param array $sidebar Sidebar instance. + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response $data + */ + public function prepare_item_for_response( $sidebar, $request ) { + $schema = $this->get_item_schema(); + $data = array(); + foreach ( $schema['properties'] as $property_id => $property ) { + if ( isset( $sidebar[ $property_id ] ) && gettype( $sidebar[ $property_id ] ) === $property['type'] ) { + $data[ $property_id ] = $sidebar[ $property_id ]; + } elseif ( isset( $property['default'] ) ) { + $data[ $property_id ] = $property['default']; + } + } + + foreach ( $sidebar['widgets'] as $widget_id => $widget ) { + $widget_data = array(); + foreach ( $schema['properties']['widgets']['items'] as $property_id => $property ) { + if ( isset( $widget[ $property_id ] ) && gettype( $widget[ $property_id ] ) === $property['type'] ) { + $widget_data[ $property_id ] = $widget[ $property_id ]; + } elseif (' settings' === $property_id && 'array' === gettype( $widget[ $property_id ] ) ) { + $widget_data[ $property_id ] = $widget['settings']; + } elseif ( isset( $property['default'] ) ) { + $widget_data[ $property_id ] = $property['default']; + } + } + $data['widgets'][ $widget_id ] = $widget_data; + } + + $response = rest_ensure_response( $data ); + + /** + * Filters a sidebar location returned from the REST API. + * + * Allows modification of the menu location data right before it is + * returned. + * + * @param WP_REST_Response $response The response object. + * @param object $sidebar The original status object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'rest_prepare_menu_location', $response, $sidebar, $request ); + } + + /** + * Retrieves the block type' schema, conforming to JSON Schema. + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'sidebar', + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'ID of sidebar.', 'gutenberg' ), + 'type' => 'string', + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'Unique name identifying the block type.', 'gutenberg' ), + 'type' => 'string', + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'description' => array( + 'description' => __( 'Description of block type.', 'gutenberg' ), + 'type' => 'string', + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'status' => array( + 'description' => __( 'Sstatus of sidebar.', 'gutenberg' ), + 'type' => 'string', + 'default' => '', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), + 'widgets' => array( + 'description' => __( 'Nested widgets.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'id_base' => array( + 'description' => __( 'Type of widget for the object.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'widget_class' => array( + 'description' => __( 'Class name of the widget implementation.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'name' => array( + 'description' => __( 'Name of the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'Description of the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'number' => array( + 'description' => __( 'Number of the widget.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'rendered' => array( + 'description' => __( 'HTML representation of the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'settings' => array( + 'description' => __( 'Settings of the widget.', 'gutenberg' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'default' => array(), + ), + ), + 'default' => array(), + 'context' => array( 'embed', 'view', 'edit' ), + ), + ), + ); + + $this->schema = $schema; + + return $this->add_additional_fields_schema( $this->schema ); + } + } From 01b9d0d4764a60e3f4b3405ed9186f48650046ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 14:46:16 +0200 Subject: [PATCH 26/41] lint --- lib/class-wp-rest-sidebars-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 501ec8b2e5991..35d0ed94e2695 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -377,7 +377,7 @@ public function prepare_item_for_response( $sidebar, $request ) { foreach ( $schema['properties']['widgets']['items'] as $property_id => $property ) { if ( isset( $widget[ $property_id ] ) && gettype( $widget[ $property_id ] ) === $property['type'] ) { $widget_data[ $property_id ] = $widget[ $property_id ]; - } elseif (' settings' === $property_id && 'array' === gettype( $widget[ $property_id ] ) ) { + } elseif ( 'settings' === $property_id && 'array' === gettype( $widget[ $property_id ] ) ) { $widget_data[ $property_id ] = $widget['settings']; } elseif ( isset( $property['default'] ) ) { $widget_data[ $property_id ] = $property['default']; From e788ff12c4e8381ff0347b022423abd220c8fe40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Thu, 6 Aug 2020 18:14:08 +0200 Subject: [PATCH 27/41] Use single, global editor instead of separate editors --- .../src/widget-area/edit/index.js | 21 +++-- .../src/widget-area/edit/inner-blocks.js | 21 +++++ .../src/components/layout/index.js | 49 +++++------ .../index.js | 81 +------------------ .../index.js | 61 +++++++++++++- packages/edit-widgets/src/store/actions.js | 12 ++- packages/edit-widgets/src/store/resolvers.js | 68 ++++++++-------- packages/edit-widgets/src/store/utils.js | 9 ++- 8 files changed, 176 insertions(+), 146 deletions(-) create mode 100644 packages/block-library/src/widget-area/edit/inner-blocks.js diff --git a/packages/block-library/src/widget-area/edit/index.js b/packages/block-library/src/widget-area/edit/index.js index a89d50b38feb3..4db0955642475 100644 --- a/packages/block-library/src/widget-area/edit/index.js +++ b/packages/block-library/src/widget-area/edit/index.js @@ -2,25 +2,34 @@ * WordPress dependencies */ import { useSelect } from '@wordpress/data'; +import { EntityProvider } from '@wordpress/core-data'; import { Panel, PanelBody } from '@wordpress/components'; -import { InnerBlocks } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import WidgetAreaInnerBlocks from './inner-blocks'; export default function WidgetAreaEdit( { clientId, className, - attributes: { name }, + attributes: { id, name }, } ) { const index = useSelect( ( select ) => select( 'core/block-editor' ).getBlockIndex( clientId ), [ clientId ] ); + return ( - + + + ); diff --git a/packages/block-library/src/widget-area/edit/inner-blocks.js b/packages/block-library/src/widget-area/edit/inner-blocks.js new file mode 100644 index 0000000000000..15ca45640a4c1 --- /dev/null +++ b/packages/block-library/src/widget-area/edit/inner-blocks.js @@ -0,0 +1,21 @@ +/** + * WordPress dependencies + */ +import { useEntityBlockEditor } from '@wordpress/core-data'; +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function WidgetAreaInnerBlocks() { + const [ blocks, onInput, onChange ] = useEntityBlockEditor( + 'root', + 'postType' + ); + return ( + + ); +} diff --git a/packages/edit-widgets/src/components/layout/index.js b/packages/edit-widgets/src/components/layout/index.js index 7684ad7c828e9..a9f1f6d95af7b 100644 --- a/packages/edit-widgets/src/components/layout/index.js +++ b/packages/edit-widgets/src/components/layout/index.js @@ -14,31 +14,32 @@ import Sidebar from '../sidebar'; import WidgetAreasBlockEditorContent from '../widget-areas-block-editor-content'; function Layout( { blockEditorSettings } ) { - const hasSidebarEnabled = useSelect( ( select ) => { - return !! select( 'core/interface' ).getActiveComplementaryArea( - 'core/edit-widgets' - ); - } ); + const hasSidebarEnabled = useSelect( + ( select ) => + !! select( 'core/interface' ).getActiveComplementaryArea( + 'core/edit-widgets' + ) + ); return ( - <> - - } - sidebar={ - hasSidebarEnabled && ( - - ) - } - content={ - - } - /> - - - - + + } + sidebar={ + hasSidebarEnabled && ( + + ) + } + content={ + + } + /> + + + ); } diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js index 7d42a6a033d36..d4bfe6af07957 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js @@ -1,40 +1,23 @@ -/** - * External dependencies - */ -import { defaultTo } from 'lodash'; - /** * WordPress dependencies */ import { Popover } from '@wordpress/components'; -import { uploadMedia } from '@wordpress/media-utils'; import { - BlockEditorProvider, + BlockList, BlockEditorKeyboardShortcuts, WritingFlow, ObserveTyping, - BlockList, } from '@wordpress/block-editor'; -import { useEntityBlockEditor } from '@wordpress/core-data'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; +import { useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import Notices from '../notices'; import KeyboardShortcuts from '../keyboard-shortcuts'; -import { KIND, POST_TYPE, buildWidgetAreaPostId } from '../../store/utils'; -const EMPTY_ARRAY = []; -export default function WidgetAreasBlockEditorContent( { - blockEditorSettings, -} ) { +export default function WidgetAreasBlockEditorContent( {} ) { const { clearSelectedBlock } = useDispatch( 'core/block-editor' ); - const widgetAreas = useSelect( ( select ) => { - const { getWidgetAreas } = select( 'core/edit-widgets' ); - return getWidgetAreas() || EMPTY_ARRAY; - } ); return ( <> @@ -54,17 +37,7 @@ export default function WidgetAreasBlockEditorContent( { > - { widgetAreas.map( ( widgetArea ) => { - return ( - - ); - } ) } +
@@ -72,49 +45,3 @@ export default function WidgetAreasBlockEditorContent( { ); } - -function WidgetAreaEditor( { widgetArea, blockEditorSettings } ) { - const { hasUploadPermissions } = useSelect( ( select ) => { - return { - hasUploadPermissions: defaultTo( - select( 'core' ).canUser( 'create', 'media' ), - true - ), - }; - } ); - - const settings = useMemo( () => { - let mediaUploadBlockEditor; - if ( hasUploadPermissions ) { - mediaUploadBlockEditor = ( { onError, ...argumentsObject } ) => { - uploadMedia( { - wpAllowedMimeTypes: blockEditorSettings.allowedMimeTypes, - onError: ( { message } ) => onError( message ), - ...argumentsObject, - } ); - }; - } - return { - ...blockEditorSettings, - mediaUpload: mediaUploadBlockEditor, - templateLock: 'all', - }; - }, [ blockEditorSettings, hasUploadPermissions ] ); - - const [ blocks, onInput, _onChange ] = useEntityBlockEditor( - KIND, - POST_TYPE, - { id: buildWidgetAreaPostId( widgetArea.id ) } - ); - - return ( - - - - ); -} diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index 7ed9f567cc35b..c932ffe4aedc4 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { defaultTo } from 'lodash'; + /** * WordPress dependencies */ @@ -6,21 +11,71 @@ import { SlotFillProvider, FocusReturnProvider, } from '@wordpress/components'; -import { BlockEditorKeyboardShortcuts } from '@wordpress/block-editor'; +import { uploadMedia } from '@wordpress/media-utils'; +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { + BlockEditorProvider, + BlockEditorKeyboardShortcuts, +} from '@wordpress/block-editor'; /** * Internal dependencies */ import KeyboardShortcuts from '../keyboard-shortcuts'; +import { useEntityBlockEditor } from '@wordpress/core-data'; +import { buildWidgetAreasPostId, KIND, POST_TYPE } from '../../store/utils'; + +export default function WidgetAreasBlockEditorProvider( { + blockEditorSettings, + ...props +} ) { + const { hasUploadPermissions } = useSelect( ( select ) => ( { + hasUploadPermissions: defaultTo( + select( 'core' ).canUser( 'create', 'media' ), + true + ), + } ) ); + + const settings = useMemo( () => { + let mediaUploadBlockEditor; + if ( hasUploadPermissions ) { + mediaUploadBlockEditor = ( { onError, ...argumentsObject } ) => { + uploadMedia( { + wpAllowedMimeTypes: blockEditorSettings.allowedMimeTypes, + onError: ( { message } ) => onError( message ), + ...argumentsObject, + } ); + }; + } + return { + ...blockEditorSettings, + mediaUpload: mediaUploadBlockEditor, + templateLock: 'all', + }; + }, [ blockEditorSettings, hasUploadPermissions ] ); + + const [ blocks, onInput, onChange ] = useEntityBlockEditor( + KIND, + POST_TYPE, + { id: buildWidgetAreasPostId() } + ); -export default function WidgetAreasBlockEditorProvider( { children } ) { return ( <> - { children } + + + diff --git a/packages/edit-widgets/src/store/actions.js b/packages/edit-widgets/src/store/actions.js index a36eb19874f98..44982e4e84fd7 100644 --- a/packages/edit-widgets/src/store/actions.js +++ b/packages/edit-widgets/src/store/actions.js @@ -65,8 +65,7 @@ export function* saveWidgetAreas( widgetAreas ) { POST_TYPE, buildWidgetAreaPostId( widgetArea.id ) ); - const widgetAreaBlock = post.blocks[ 0 ]; - const widgetsBlocks = widgetAreaBlock.innerBlocks; + const widgetsBlocks = post.blocks; const newWidgets = widgetsBlocks.map( ( block ) => { const widgetId = clientIdToWidgetId[ block.clientId ]; const oldWidget = widgets[ widgetId ]; @@ -89,6 +88,15 @@ export function* saveWidgetAreas( widgetAreas ) { WIDGET_AREA_ENTITY_TYPE, widgetArea.id ); + + yield dispatch( + 'core', + 'receiveEntityRecords', + KIND, + POST_TYPE, + post, + undefined + ); } // saveEditedEntityRecord resets the resolution status, let's fix it manually diff --git a/packages/edit-widgets/src/store/resolvers.js b/packages/edit-widgets/src/store/resolvers.js index a8be42d326bb9..52cd625c4f3ab 100644 --- a/packages/edit-widgets/src/store/resolvers.js +++ b/packages/edit-widgets/src/store/resolvers.js @@ -13,6 +13,7 @@ import { WIDGET_AREA_ENTITY_TYPE, buildWidgetAreasQuery, buildWidgetAreaPostId, + buildWidgetAreasPostId, } from './utils'; import { transformWidgetToBlock } from './transformers'; @@ -27,61 +28,62 @@ export function* getWidgetAreas() { query ); + const widgetAreaBlocks = []; const widgetIdToClientId = {}; for ( const widgetArea of widgetAreas ) { - const blocks = []; + const widgetBlocks = []; for ( const widget of widgetArea.widgets ) { const block = transformWidgetToBlock( widget ); widgetIdToClientId[ widget.id ] = block.clientId; - blocks.push( block ); + widgetBlocks.push( block ); } // Persist the actual post containing the navigation block - const widgetAreaBlock = createBlock( - 'core/widget-area', - { + yield persistStubPost( + buildWidgetAreaPostId( widgetArea.id ), + widgetBlocks + ); + + widgetAreaBlocks.push( + createBlock( 'core/widget-area', { id: widgetArea.id, name: widgetArea.name, - }, - blocks + } ) ); - - // Dispatch startResolution and finishResolution to skip the execution of the real getEntityRecord resolver - it would - // issue an http request and fail. - const stubPost = createStubPost( widgetArea.id, widgetAreaBlock ); - const args = [ KIND, POST_TYPE, stubPost.id ]; - yield dispatch( 'core', 'startResolution', 'getEntityRecord', args ); - yield persistPost( stubPost ); - yield dispatch( 'core', 'finishResolution', 'getEntityRecord', args ); } + yield persistStubPost( buildWidgetAreasPostId(), widgetAreaBlocks ); + yield { type: 'SET_WIDGET_TO_CLIENT_ID_MAPPING', mapping: widgetIdToClientId, }; } -const createStubPost = ( widgetAreaId, widgetAreaBlock ) => { - const id = buildWidgetAreaPostId( widgetAreaId ); - return { - id, - slug: id, - status: 'draft', - type: 'page', - blocks: [ widgetAreaBlock ], - meta: { - widgetAreaId, - }, - }; -}; - -const persistPost = ( post ) => - dispatch( +const persistStubPost = function* ( id, blocks ) { + const stubPost = createStubPost( id, blocks ); + const args = [ KIND, POST_TYPE, id ]; + yield dispatch( 'core', 'startResolution', 'getEntityRecord', args ); + yield dispatch( 'core', 'receiveEntityRecords', KIND, POST_TYPE, - post, - { id: post.id }, + stubPost, + { id: stubPost.id }, false ); + yield dispatch( 'core', 'finishResolution', 'getEntityRecord', args ); + return stubPost; +}; + +const createStubPost = ( id, blocks ) => ( { + id, + slug: id, + status: 'draft', + type: 'page', + blocks, + meta: { + widgetAreaId: id, + }, +} ); diff --git a/packages/edit-widgets/src/store/utils.js b/packages/edit-widgets/src/store/utils.js index a3d9452e2def5..ad5ba71d1e25a 100644 --- a/packages/edit-widgets/src/store/utils.js +++ b/packages/edit-widgets/src/store/utils.js @@ -20,7 +20,7 @@ export const WIDGET_AREA_ENTITY_TYPE = 'widgetArea'; export const POST_TYPE = 'postType'; /** - * Builds an ID for a new navigation post. + * Builds an ID for a new widget area post. * * @param {number} widgetAreaId Widget area id. * @return {string} An ID. @@ -28,6 +28,13 @@ export const POST_TYPE = 'postType'; export const buildWidgetAreaPostId = ( widgetAreaId ) => `widget-area-${ widgetAreaId }`; +/** + * Builds an ID for a global widget areas post. + * + * @return {string} An ID. + */ +export const buildWidgetAreasPostId = () => `widget-areas`; + /** * Builds a query to resolve sidebars. * From 1ca58bb4660b25a9ad8680b1fd7be78525d3c095 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 7 Aug 2020 11:15:26 +0200 Subject: [PATCH 28/41] Update lib/class-wp-rest-sidebars-controller.php Co-authored-by: Robert Anderson --- lib/class-wp-rest-sidebars-controller.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 35d0ed94e2695..e9c69736e09f0 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -247,7 +247,7 @@ public function get_item( $request ) { * * Note: The id can be either an index, the id or the name of a sidebar * - * @param string $id ID of the sidebar. + * @param string|int $id ID of the sidebar. * * @return array|null * @global array $wp_registered_sidebars @@ -503,4 +503,3 @@ public function get_item_schema() { } } - From 89b7994995525374b35c1829425adbaf38e26c5a Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 7 Aug 2020 11:15:37 +0200 Subject: [PATCH 29/41] Update lib/class-wp-rest-widget-utils-controller.php Co-authored-by: Robert Anderson --- lib/class-wp-rest-widget-utils-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-rest-widget-utils-controller.php b/lib/class-wp-rest-widget-utils-controller.php index 1d46572103519..fca03ae5e420d 100644 --- a/lib/class-wp-rest-widget-utils-controller.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -150,7 +150,7 @@ public function compute_widget_form( $request ) { global $wp_widget_factory; $widget_obj = $wp_widget_factory->widgets[ $widget_class ]; - $widget_obj->_set( - 1 ); + $widget_obj->_set( -1 ); ob_start(); $instance = apply_filters( 'widget_form_callback', $instance, $widget_obj ); From fa1c05029bd83e602c3be7ca060a299a7bafd428 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 7 Aug 2020 11:20:40 +0200 Subject: [PATCH 30/41] Update packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js Co-authored-by: Robert Anderson --- .../src/components/widget-areas-block-editor-content/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js index d4bfe6af07957..92f82f5b4325d 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-content/index.js @@ -16,7 +16,7 @@ import { useDispatch } from '@wordpress/data'; import Notices from '../notices'; import KeyboardShortcuts from '../keyboard-shortcuts'; -export default function WidgetAreasBlockEditorContent( {} ) { +export default function WidgetAreasBlockEditorContent() { const { clearSelectedBlock } = useDispatch( 'core/block-editor' ); return ( From 9fc82ece40795d4872a9b97d695e8033ec79db22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 11:14:53 +0200 Subject: [PATCH 31/41] Remove lib/class-experimental-wp-widget-blocks-manager.php --- ...-experimental-wp-widget-blocks-manager.php | 154 ------------------ lib/class-wp-rest-sidebars-controller.php | 76 ++++++++- lib/customizer.php | 26 --- lib/load.php | 3 - 4 files changed, 75 insertions(+), 184 deletions(-) delete mode 100644 lib/class-experimental-wp-widget-blocks-manager.php diff --git a/lib/class-experimental-wp-widget-blocks-manager.php b/lib/class-experimental-wp-widget-blocks-manager.php deleted file mode 100644 index ad73c9e29a908..0000000000000 --- a/lib/class-experimental-wp-widget-blocks-manager.php +++ /dev/null @@ -1,154 +0,0 @@ -_set( $number ); - - $instances = $object->get_settings(); - $instance = $instances[ $number ]; - - $args = array_merge( - $sidebar, - array( - 'widget_id' => $id, - 'widget_name' => $name, - ) - ); - - /** - * Filters the settings for a particular widget instance. - * - * Returning false will effectively short-circuit display of the widget. - * - * @since 2.8.0 - * - * @param array $instance The current widget instance's settings. - * @param WP_Widget $this The current widget instance. - * @param array $args An array of default widget arguments. - */ - $instance = apply_filters( 'widget_display_callback', $instance, $object, $args ); - - if ( false === $instance ) { - return array(); - } - - return $instance; - } - - /** - * Given a widget id returns an array containing information about the widget. - * - * @since 5.7.0 - * - * @param string $widget_id Identifier of the widget. - * @return array Array containing the the widget object, the number, and the name. - */ - private static function get_widget_info( $widget_id ) { - $wp_registered_widgets = self::get_wp_registered_widgets(); - - if ( - ! isset( $wp_registered_widgets[ $widget_id ]['callback'][0] ) || - ! isset( $wp_registered_widgets[ $widget_id ]['params'][0]['number'] ) || - ! isset( $wp_registered_widgets[ $widget_id ]['name'] ) || - ! ( $wp_registered_widgets[ $widget_id ]['callback'][0] instanceof WP_Widget ) - ) { - return array( null, null, null ); - } - - $object = $wp_registered_widgets[ $widget_id ]['callback'][0]; - $number = $wp_registered_widgets[ $widget_id ]['params'][0]['number']; - $name = $wp_registered_widgets[ $widget_id ]['name']; - return array( $object, $number, $name ); - } - -} diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index e9c69736e09f0..3636cc44efe76 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -334,7 +334,7 @@ public static function get_widgets( $sidebar_id ) { if ( isset( $widget['callback'][0] ) ) { $instance = $widget['callback'][0]; $widget['widget_class'] = get_class( $instance ); - $widget['settings'] = \Experimental_WP_Widget_Blocks_Manager::get_sidebar_widget_instance( + $widget['settings'] = static::get_sidebar_widget_instance( $wp_registered_sidebars[ $sidebar_id ], $widget_id ); @@ -502,4 +502,78 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } + /** + * Retrieves a widget instance. + * + * @since 5.7.0 + * + * @param array $sidebar sidebar data available at $wp_registered_sidebars. + * @param string $id Identifier of the widget instance. + * @return array Array containing the widget instance. + */ + public static function get_sidebar_widget_instance( $sidebar, $id ) { + list( $object, $number, $name ) = static::get_widget_info( $id ); + if ( ! $object ) { + return array(); + } + + $object->_set( $number ); + + $instances = $object->get_settings(); + $instance = $instances[ $number ]; + + $args = array_merge( + $sidebar, + array( + 'widget_id' => $id, + 'widget_name' => $name, + ) + ); + + /** + * Filters the settings for a particular widget instance. + * + * Returning false will effectively short-circuit display of the widget. + * + * @since 2.8.0 + * + * @param array $instance The current widget instance's settings. + * @param WP_Widget $this The current widget instance. + * @param array $args An array of default widget arguments. + */ + $instance = apply_filters( 'widget_display_callback', $instance, $object, $args ); + + if ( false === $instance ) { + return array(); + } + + return $instance; + } + + /** + * Given a widget id returns an array containing information about the widget. + * + * @since 5.7.0 + * + * @param string $widget_id Identifier of the widget. + * @return array Array containing the the widget object, the number, and the name. + */ + private static function get_widget_info( $widget_id ) { + global $wp_registered_widgets; + + if ( + ! isset( $wp_registered_widgets[ $widget_id ]['callback'][0] ) || + ! isset( $wp_registered_widgets[ $widget_id ]['params'][0]['number'] ) || + ! isset( $wp_registered_widgets[ $widget_id ]['name'] ) || + ! ( $wp_registered_widgets[ $widget_id ]['callback'][0] instanceof WP_Widget ) + ) { + return array( null, null, null ); + } + + $object = $wp_registered_widgets[ $widget_id ]['callback'][0]; + $number = $wp_registered_widgets[ $widget_id ]['params'][0]['number']; + $name = $wp_registered_widgets[ $widget_id ]['name']; + return array( $object, $number, $name ); + } + } diff --git a/lib/customizer.php b/lib/customizer.php index 1f27db00ee308..f665564517c12 100644 --- a/lib/customizer.php +++ b/lib/customizer.php @@ -74,32 +74,6 @@ function gutenberg_customize_register( $wp_customize ) { } add_action( 'customize_register', 'gutenberg_customize_register' ); -/** - * Specifies how to save the `gutenberg_widget_blocks` setting. It parses the JSON string and updates the - * referenced widget areas with the new content. - * - * @param string $value The value that is being published. - * @param \WP_Customize_Setting $setting The setting instance. - */ -function gutenberg_customize_update( $value, $setting ) { - foreach ( json_decode( $value ) as $sidebar_id => $sidebar_content ) { - $id_referenced_in_sidebar = Experimental_WP_Widget_Blocks_Manager::get_post_id_referenced_in_sidebar( $sidebar_id ); - - $post_id = wp_insert_post( - array( - 'ID' => $id_referenced_in_sidebar, - 'post_content' => $sidebar_content, - 'post_type' => 'wp_area', - ) - ); - - if ( 0 === $id_referenced_in_sidebar ) { - Experimental_WP_Widget_Blocks_Manager::reference_post_id_in_sidebar( $sidebar_id, $post_id ); - } - } -} -add_action( 'customize_update_gutenberg_widget_blocks', 'gutenberg_customize_update', 10, 2 ); - /** * Filters the Customizer widget settings arguments. * This is needed because the Customizer registers settings for the raw registered widgets, without going through the `sidebars_widgets` filter. diff --git a/lib/load.php b/lib/load.php index a1573212c006b..666f9368a766c 100644 --- a/lib/load.php +++ b/lib/load.php @@ -32,9 +32,6 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_REST_Widget_Utils_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-widget-utils-controller.php'; } - if ( ! class_exists( 'Experimental_WP_Widget_Blocks_Manager' ) ) { - require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php'; - } if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php'; } From 30b5b7518b8a58ceb69151daa4a9cdc97700513f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 11:21:02 +0200 Subject: [PATCH 32/41] Clean up the endpoints --- lib/class-wp-rest-sidebars-controller.php | 7 ++++--- lib/class-wp-widget-block.php | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 3636cc44efe76..b103d8be71eda 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -424,22 +424,23 @@ public function get_item_schema() { 'readonly' => true, ), 'name' => array( - 'description' => __( 'Unique name identifying the block type.', 'gutenberg' ), + 'description' => __( 'Unique name identifying the sidebar.', 'gutenberg' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'description' => array( - 'description' => __( 'Description of block type.', 'gutenberg' ), + 'description' => __( 'Description of sidebar.', 'gutenberg' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), 'status' => array( - 'description' => __( 'Sstatus of sidebar.', 'gutenberg' ), + 'description' => __( 'Status of sidebar.', 'gutenberg' ), 'type' => 'string', + 'enum' => array( 'active', 'inactive' ), 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, diff --git a/lib/class-wp-widget-block.php b/lib/class-wp-widget-block.php index 350f479cd0a5a..3d0d234221585 100644 --- a/lib/class-wp-widget-block.php +++ b/lib/class-wp-widget-block.php @@ -19,7 +19,6 @@ class WP_Widget_Block extends WP_Widget { * @var array */ protected $default_instance = array( - 'title' => '', 'content' => '', ); @@ -68,7 +67,6 @@ public function widget( $args, $instance ) { */ public function update( $new_instance, $old_instance ) { $instance = array_merge( $this->default_instance, $old_instance ); - $instance['title'] = sanitize_text_field( $new_instance['title'] ); $instance['content'] = $new_instance['content']; return $instance; } @@ -84,7 +82,6 @@ public function form( $instance ) { $instance = wp_parse_args( (array) $instance, $this->default_instance ); echo do_blocks( $instance['content'] ); ?> - Date: Fri, 7 Aug 2020 11:28:12 +0200 Subject: [PATCH 33/41] Invoke getWidgetAreas at the top level --- .../src/components/widget-areas-block-editor-provider/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js index c932ffe4aedc4..6815470efe242 100644 --- a/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js +++ b/packages/edit-widgets/src/components/widget-areas-block-editor-provider/index.js @@ -35,6 +35,7 @@ export default function WidgetAreasBlockEditorProvider( { select( 'core' ).canUser( 'create', 'media' ), true ), + widgetAreas: select( 'core/edit-widgets' ).getWidgetAreas(), } ) ); const settings = useMemo( () => { From 176cc3c33c69ae8396d15e5bee536b0966c7419b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 11:40:27 +0200 Subject: [PATCH 34/41] rename widgetArea entity to sidebars --- packages/core-data/src/entities.js | 4 ++-- packages/edit-widgets/src/store/controls.js | 16 +++++++++------- packages/edit-widgets/src/store/utils.js | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index b487b9c9ad093..4918c661c3601 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -55,10 +55,10 @@ export const defaultEntities = [ label: __( 'Taxonomy' ), }, { - name: 'widgetArea', + name: 'sidebar', kind: 'root', baseURL: '/__experimental/sidebars', - plural: 'widgetAreas', + plural: 'sidebars', transientEdits: { blocks: true }, label: __( 'Widget areas' ), }, diff --git a/packages/edit-widgets/src/store/controls.js b/packages/edit-widgets/src/store/controls.js index 7998e814f8c32..1b836b5be1aaf 100644 --- a/packages/edit-widgets/src/store/controls.js +++ b/packages/edit-widgets/src/store/controls.js @@ -6,7 +6,7 @@ import { createRegistryControl } from '@wordpress/data'; /** * Internal dependencies */ -import { buildWidgetAreasQuery } from './utils'; +import { buildWidgetAreasQuery, KIND, WIDGET_AREA_ENTITY_TYPE } from './utils'; /** * Trigger an API Fetch request. @@ -83,7 +83,7 @@ export function getNavigationPostForMenu( menuId ) { */ export function resolveWidgetAreas( query = buildWidgetAreasQuery() ) { return { - type: 'RESOLVE_SIDEBARS', + type: 'RESOLVE_WIDGET_AREAS', query, }; } @@ -156,11 +156,13 @@ const controls = { } ), - RESOLVE_SIDEBARS: createRegistryControl( ( registry ) => ( { query } ) => { - return registry - .__experimentalResolveSelect( 'core' ) - .getEntityRecords( 'root', 'widgetArea', query ); //getWidgetAreas( query ); - } ), + RESOLVE_WIDGET_AREAS: createRegistryControl( + ( registry ) => ( { query } ) => { + return registry + .__experimentalResolveSelect( 'core' ) + .getEntityRecords( KIND, WIDGET_AREA_ENTITY_TYPE, query ); + } + ), }; const getState = ( registry ) => diff --git a/packages/edit-widgets/src/store/utils.js b/packages/edit-widgets/src/store/utils.js index ad5ba71d1e25a..ba47f57f8320e 100644 --- a/packages/edit-widgets/src/store/utils.js +++ b/packages/edit-widgets/src/store/utils.js @@ -10,7 +10,7 @@ export const KIND = 'root'; * * @type {string} */ -export const WIDGET_AREA_ENTITY_TYPE = 'widgetArea'; +export const WIDGET_AREA_ENTITY_TYPE = 'sidebar'; /** * "post type" of the widget area post. From 3f4fd5ab169467a3199ed8d5dac6fb0e75d1f71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 11:51:15 +0200 Subject: [PATCH 35/41] Enable the experiment code conditionally --- gutenberg.php | 4 +++- lib/load.php | 18 ++++++++++-------- lib/rest-api.php | 12 ++++++++---- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 5dba248e1eb21..c9269207a4ab0 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -186,7 +186,9 @@ function register_site_icon_url( $response ) { * Registers the WP_Widget_Block widget */ function gutenberg_register_widgets() { - register_widget( 'WP_Widget_Block' ); + if ( gutenberg_is_experiment_enabled( 'gutenberg-widget-experiments' ) ) { + register_widget( 'WP_Widget_Block' ); + } } add_action( 'widgets_init', 'gutenberg_register_widgets' ); diff --git a/lib/load.php b/lib/load.php index 666f9368a766c..1c35e43e6915b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -29,8 +29,16 @@ function gutenberg_is_experiment_enabled( $name ) { /** * Start: Include for phase 2 */ - if ( ! class_exists( 'WP_REST_Widget_Utils_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-widget-utils-controller.php'; + if ( gutenberg_is_experiment_enabled( 'gutenberg-widget-experiments' ) ) { + if ( ! class_exists( 'WP_REST_Widget_Utils_Controller' ) ) { + require dirname( __FILE__ ) . '/class-wp-rest-widget-utils-controller.php'; + } + if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) { + require_once dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php'; + } + if ( ! class_exists( 'WP_Widget_Block' ) ) { + require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; + } } if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php'; @@ -56,12 +64,6 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_REST_Plugins_Controller' ) ) { require_once dirname( __FILE__ ) . '/class-wp-rest-plugins-controller.php'; } - if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php'; - } - if ( ! class_exists( 'WP_Widget_Block' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; - } /** * End: Include for phase 2 */ diff --git a/lib/rest-api.php b/lib/rest-api.php index 32ca31beaeefa..e8f3a2bc7827d 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -136,8 +136,10 @@ function gutenberg_filter_rest_prepare_theme( $response, $theme, $request ) { * @since 5.0.0 */ function gutenberg_register_rest_widget_updater_routes() { - $widget_forms = new WP_REST_Widget_Utils_Controller(); - $widget_forms->register_routes(); + if ( gutenberg_is_experiment_enabled( 'gutenberg-widget-experiments' ) ) { + $widget_forms = new WP_REST_Widget_Utils_Controller(); + $widget_forms->register_routes(); + } } add_action( 'rest_api_init', 'gutenberg_register_rest_widget_updater_routes' ); @@ -192,8 +194,10 @@ function gutenberg_register_plugins_endpoint() { * Registers the Sidebars REST API routes. */ function gutenberg_register_sidebars_endpoint() { - $sidebars = new WP_REST_Sidebars_Controller(); - $sidebars->register_routes(); + if ( gutenberg_is_experiment_enabled( 'gutenberg-widget-experiments' ) ) { + $sidebars = new WP_REST_Sidebars_Controller(); + $sidebars->register_routes(); + } } add_action( 'rest_api_init', 'gutenberg_register_sidebars_endpoint' ); From 97c2b7a2f879390a629728ec54cd1587f3bb017f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 12:35:42 +0200 Subject: [PATCH 36/41] Load Gutenberg styles on widgets.php --- lib/load.php | 11 +++++++---- lib/widgets-page.php | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/load.php b/lib/load.php index 1c35e43e6915b..59ed5e0e838f9 100644 --- a/lib/load.php +++ b/lib/load.php @@ -17,6 +17,7 @@ * @param string $name The name of the experiment. * * @return bool True when the experiment is enabled. + * */ function gutenberg_is_experiment_enabled( $name ) { $experiments = get_option( 'gutenberg-experiments' ); @@ -36,9 +37,6 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) { require_once dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php'; } - if ( ! class_exists( 'WP_Widget_Block' ) ) { - require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; - } } if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php'; @@ -86,6 +84,12 @@ function gutenberg_is_experiment_enabled( $name ) { if ( ! class_exists( 'WP_Block_List' ) ) { require dirname( __FILE__ ) . '/class-wp-block-list.php'; } +if ( gutenberg_is_experiment_enabled( 'gutenberg-widget-experiments' ) ) { + if ( ! class_exists( 'WP_Widget_Block' ) ) { + require_once dirname( __FILE__ ) . '/class-wp-widget-block.php'; + } + require_once dirname( __FILE__ ) . '/widgets-page.php'; +} require dirname( __FILE__ ) . '/compat.php'; require dirname( __FILE__ ) . '/utils.php'; @@ -99,7 +103,6 @@ function gutenberg_is_experiment_enabled( $name ) { require dirname( __FILE__ ) . '/block-directory.php'; require dirname( __FILE__ ) . '/demo.php'; require dirname( __FILE__ ) . '/widgets.php'; -require dirname( __FILE__ ) . '/widgets-page.php'; require dirname( __FILE__ ) . '/navigation-page.php'; require dirname( __FILE__ ) . '/experiments-page.php'; require dirname( __FILE__ ) . '/customizer.php'; diff --git a/lib/widgets-page.php b/lib/widgets-page.php index a6dae5ee90bab..61fdfca640d7d 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -36,8 +36,8 @@ class="blocks-widgets-container * @param string $hook Page. */ function gutenberg_widgets_init( $hook ) { - if ( 'gutenberg_page_gutenberg-widgets' !== $hook && 'gutenberg_customizer' !== $hook ) { - return; + if ( ! in_array( $hook, array( 'gutenberg_page_gutenberg-widgets', 'gutenberg_customizer', 'widgets.php' ) ) ) { + return; } $initializer_name = 'gutenberg_page_gutenberg-widgets' === $hook From f3cf1abee0a735e82873eea4df1c49fa2debfacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 13:22:42 +0200 Subject: [PATCH 37/41] Make the API compatible with old-style widgets --- lib/class-wp-rest-sidebars-controller.php | 57 ++++++++++++----------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index b103d8be71eda..07cdc2613252e 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -139,45 +139,46 @@ public function update_item( $request ) { $sidebar_id = $request['id']; $input_widgets = $request['widgets']; + // Initialize $numbers $numbers = array(); foreach ( $wp_registered_widget_updates as $id_base => $control ) { - $numbers[ $id_base ] = $control['callback'][0]->number + 1; + if ( is_array( $control['callback'] ) ) { + $numbers[ $id_base ] = $control['callback'][0]->number + 1; + } } - $created_widgets = array(); - // Create and update widgets. $sidebar_widgets_ids = array(); foreach ( $input_widgets as $input_widget ) { - if ( ! isset( $wp_registered_widget_updates[ $input_widget['id_base'] ] ) ) { - continue; - } - $update_control = $wp_registered_widget_updates[ $input_widget['id_base'] ]; - if ( ! isset( $input_widget['id'] ) ) { - $number = $numbers[ $input_widget['id_base'] ] ++; - $id = $input_widget['id_base'] . '-' . $number; - - $input_widget['id'] = $id; - $input_widget['number'] = $number; - $created_widgets[] = array( - 'id' => $id, - 'number' => $number, - 'id_base' => $input_widget['id_base'], - ); + ob_start(); + if ( isset( $wp_registered_widget_updates[ $input_widget['id_base'] ] ) ) { + // Class-based widget + $update_control = $wp_registered_widget_updates[ $input_widget['id_base'] ]; + if ( ! isset( $input_widget['id'] ) ) { + $number = $numbers[ $input_widget['id_base'] ] ++; + $id = $input_widget['id_base'] . '-' . $number; + + $input_widget['id'] = $id; + $input_widget['number'] = $number; + } + $field = 'widget-' . $input_widget['id_base']; + $number = $input_widget['number']; + $_POST = $input_widget; + $_POST[ $field ][ $number ] = wp_slash( $input_widget['settings'] ); + call_user_func( $update_control['callback'] ); + $update_control['callback'][0]->updated = false; + } elseif ( $wp_registered_widget_updates[ $input_widget['id'] ] ) { + // Old-style widget + $update_control = $wp_registered_widget_updates[ $input_widget['id'] ]; + $_POST = wp_slash( $input_widget['settings'] ); + call_user_func( $update_control['callback'] ); } - - $field = 'widget-' . $input_widget['id_base']; - $number = $input_widget['number']; - $_POST = $input_widget; - - $_POST[ $field ][ $number ] = wp_slash( $input_widget['settings'] ); - call_user_func_array( $update_control['callback'], array() ); - $update_control['callback'][0]->updated = false; + ob_end_clean(); $sidebar_widgets_ids[] = $input_widget['id']; } - // Update sidebar to only have the widgets we just processed. + // Update sidebar to only consist of the widgets we just processed. $sidebars = wp_get_sidebars_widgets(); $sidebars[ $sidebar_id ] = $sidebar_widgets_ids; wp_set_sidebars_widgets( $sidebars ); @@ -331,7 +332,7 @@ public static function get_widgets( $sidebar_id ) { $widget['rendered'] = ob_get_clean(); } - if ( isset( $widget['callback'][0] ) ) { + if ( is_array( $widget['callback'] ) && isset( $widget['callback'][0] ) ) { $instance = $widget['callback'][0]; $widget['widget_class'] = get_class( $instance ); $widget['settings'] = static::get_sidebar_widget_instance( From 07db7d1697d4f4879b7670b7ab4393784251d99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 13:29:29 +0200 Subject: [PATCH 38/41] Clean up WP_REST_Widget_Utils_Controller --- lib/class-wp-rest-widget-utils-controller.php | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/lib/class-wp-rest-widget-utils-controller.php b/lib/class-wp-rest-widget-utils-controller.php index fca03ae5e420d..7f3853329163b 100644 --- a/lib/class-wp-rest-widget-utils-controller.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -48,11 +48,6 @@ public function register_routes() { 'type' => 'object', 'default' => array(), ), - 'instance_changes' => array( - 'description' => __( 'Array of instance changes', 'gutenberg' ), - 'type' => 'object', - 'default' => array(), - ), ), array( 'methods' => WP_REST_Server::EDITABLE, @@ -61,35 +56,6 @@ public function register_routes() { ), ) ); - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/preview/(?P[^/]*)/', - array( - 'args' => array( - 'widget_class' => array( - 'description' => __( 'Class name of the widget.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - 'validate_callback' => array( $this, 'is_valid_widget' ), - ), - 'instance' => array( - 'description' => __( 'Current widget instance', 'gutenberg' ), - 'type' => 'object', - 'default' => array(), - ), - 'instance_changes' => array( - 'description' => __( 'Array of instance changes', 'gutenberg' ), - 'type' => 'object', - 'default' => array(), - ), - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'permission_callback' => array( $this, 'permissions_check' ), - 'callback' => array( $this, 'compute_widget_preview' ), - ), - ) - ); } /** From 13c3db052ea100315fe3c892e2a88204a6bdb287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 13:37:59 +0200 Subject: [PATCH 39/41] revert code data layer change --- packages/core-data/src/resolvers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 076cecc2e7a26..96a87f9a79a7b 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -54,7 +54,7 @@ export function* getCurrentUser() { export function* getEntityRecord( kind, name, key = '' ) { const entities = yield getKindEntities( kind ); const entity = find( entities, { kind, name } ); - if ( entity ) { + if ( ! entity ) { return; } const record = yield apiFetch( { From 48c7c35f7d87261e0d91c551b6523b239386e378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 16:23:08 +0200 Subject: [PATCH 40/41] Add unit tests for the endpoints --- lib/class-wp-rest-sidebars-controller.php | 235 +++++----- lib/class-wp-rest-widget-utils-controller.php | 4 +- lib/load.php | 1 - lib/widgets-page.php | 2 +- .../class-rest-sidebars-controller-test.php | 410 ++++++++++++++++++ 5 files changed, 539 insertions(+), 113 deletions(-) create mode 100644 phpunit/class-rest-sidebars-controller-test.php diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 07cdc2613252e..795e68951d20c 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -55,9 +55,11 @@ public function register_routes() { '/' . $this->rest_base, array( array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'permissions_check' ), ), + 'schema' => array( $this, 'get_public_item_schema' ), ) ); @@ -75,7 +77,7 @@ public function register_routes() { 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), 'type' => 'string', 'validate_callback' => function ( $id ) { - return ! is_null( self::get_sidebar( $id ) ); + return self::get_sidebar( $id )[0]; }, ), ), @@ -84,20 +86,9 @@ public function register_routes() { 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'permissions_check' ), - 'args' => array( - 'id' => array( - 'description' => __( 'The id of a registered sidebar', 'gutenberg' ), - 'type' => 'string', - 'validate_callback' => function ( $id ) { - return ! is_null( self::get_sidebar( $id ) ); - }, - ), - 'widgets' => array( - 'description' => __( 'The list of widgets to save', 'gutenberg' ), - 'type' => 'array', - ), - ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), ), + 'schema' => array( $this, 'get_public_item_schema' ), ) ); } @@ -135,11 +126,11 @@ public function permissions_check() { * @global array $wp_registered_widget_updates */ public function update_item( $request ) { - global $wp_registered_widget_updates; + global $wp_registered_widget_updates, $wp_registered_widgets; $sidebar_id = $request['id']; $input_widgets = $request['widgets']; - // Initialize $numbers + // Initialize $numbers. $numbers = array(); foreach ( $wp_registered_widget_updates as $id_base => $control ) { if ( is_array( $control['callback'] ) ) { @@ -152,7 +143,7 @@ public function update_item( $request ) { foreach ( $input_widgets as $input_widget ) { ob_start(); if ( isset( $wp_registered_widget_updates[ $input_widget['id_base'] ] ) ) { - // Class-based widget + // Class-based widget. $update_control = $wp_registered_widget_updates[ $input_widget['id_base'] ]; if ( ! isset( $input_widget['id'] ) ) { $number = $numbers[ $input_widget['id_base'] ] ++; @@ -161,16 +152,34 @@ public function update_item( $request ) { $input_widget['id'] = $id; $input_widget['number'] = $number; } - $field = 'widget-' . $input_widget['id_base']; - $number = $input_widget['number']; - $_POST = $input_widget; + $field = 'widget-' . $input_widget['id_base']; + $number = $input_widget['number']; + $_POST = $input_widget; $_POST[ $field ][ $number ] = wp_slash( $input_widget['settings'] ); call_user_func( $update_control['callback'] ); $update_control['callback'][0]->updated = false; + + // Just because we saved new widget doesn't mean it was added to $wp_registered_widgets. + // Let's make sure it's there so that it's included in the response. + if ( ! isset( $wp_registered_widgets[ $input_widget['id'] ] ) ) { + $first_widget_id = substr( $input_widget['id'], 0, strrpos( $input_widget['id'], '-' ) ) . '-1'; + + if ( isset( $wp_registered_widgets[ $first_widget_id ] ) ) { + $wp_registered_widgets[ $input_widget['id'] ] = $wp_registered_widgets[ $first_widget_id ]; + $widget_class = get_class( $update_control['callback'][0] ); + $new_object = new $widget_class( + $input_widget['id_base'], + $input_widget['name'], + $input_widget['settings'] + ); + $new_object->_register(); + $wp_registered_widgets[ $input_widget['id'] ]['callback'][0] = $new_object; + } + } } elseif ( $wp_registered_widget_updates[ $input_widget['id'] ] ) { - // Old-style widget + // Old-style widget. $update_control = $wp_registered_widget_updates[ $input_widget['id'] ]; - $_POST = wp_slash( $input_widget['settings'] ); + $_POST = wp_slash( $input_widget['settings'] ); call_user_func( $update_control['callback'] ); } ob_end_clean(); @@ -198,26 +207,9 @@ public function update_item( $request ) { * @global array $wp_registered_sidebars */ public function get_items( $request ) { - global $wp_registered_sidebars; - $fields = $this->get_fields_for_response( $request ); - $data = array(); foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) { - $sidebar = compact( 'id', 'widgets' ); - - if ( isset( $wp_registered_sidebars[ $id ] ) ) { - $registered_sidebar = $wp_registered_sidebars[ $id ]; - - $sidebar['status'] = 'active'; - $sidebar['name'] = $registered_sidebar['name']; - $sidebar['description'] = $registered_sidebar['description']; - } else { - $sidebar['status'] = 'inactive'; - } - - if ( rest_is_field_included( 'widgets', $fields ) ) { - $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); - } + $sidebar = self::get_sidebar( $id )[0]; $data[] = $this->prepare_item_for_response( $sidebar, $request )->get_data(); } @@ -233,12 +225,7 @@ public function get_items( $request ) { * @return WP_REST_Response */ public function get_item( $request ) { - $fields = $this->get_fields_for_response( $request ); - $sidebar = self::get_sidebar( $request['id'] ); - - if ( rest_is_field_included( 'widgets', $fields ) ) { - $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); - } + $sidebar = self::get_sidebar( $request['id'] )[0]; return $this->prepare_item_for_response( $sidebar, $request ); } @@ -263,18 +250,18 @@ public static function get_sidebar( $id ) { foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { if ( sanitize_title( $sidebar['name'] ) === $id ) { - return $sidebar; + return array( true, $sidebar ); } } } foreach ( (array) $wp_registered_sidebars as $key => $sidebar ) { if ( $key === $id ) { - return $sidebar; + return array( true, $sidebar ); } } - return null; + return array( false, array( 'id' => $id ) ); } /** @@ -322,8 +309,14 @@ public static function get_widgets( $sidebar_id ) { $classname .= '_' . get_class( $cn ); } } - $classname = ltrim( $classname, '_' ); - $widget_parameters[0]['before_widget'] = sprintf( $widget_parameters[0]['before_widget'], $widget_id, $classname ); + $classname = ltrim( $classname, '_' ); + if ( isset( $widget_parameters[0]['before_widget'] ) ) { + $widget_parameters[0]['before_widget'] = sprintf( + $widget_parameters[0]['before_widget'], + $widget_id, + $classname + ); + } ob_start(); @@ -357,12 +350,32 @@ public static function get_widgets( $sidebar_id ) { /** * Prepare a single sidebar output for response * - * @param array $sidebar Sidebar instance. - * @param WP_REST_Request $request Request object. + * @param array $raw_sidebar Sidebar instance. + * @param WP_REST_Request $request Request object. * * @return WP_REST_Response $data */ - public function prepare_item_for_response( $sidebar, $request ) { + public function prepare_item_for_response( $raw_sidebar, $request ) { + global $wp_registered_sidebars; + + $id = $raw_sidebar['id']; + $sidebar = array( 'id' => $id ); + + if ( isset( $wp_registered_sidebars[ $id ] ) ) { + $registered_sidebar = $wp_registered_sidebars[ $id ]; + + $sidebar['status'] = 'active'; + $sidebar['name'] = isset( $registered_sidebar['name'] ) ? $registered_sidebar['name'] : ''; + $sidebar['description'] = isset( $registered_sidebar['description'] ) ? $registered_sidebar['description'] : ''; + } else { + $sidebar['status'] = 'inactive'; + } + + $fields = $this->get_fields_for_response( $request ); + if ( rest_is_field_included( 'widgets', $fields ) ) { + $sidebar['widgets'] = self::get_widgets( $sidebar['id'] ); + } + $schema = $this->get_item_schema(); $data = array(); foreach ( $schema['properties'] as $property_id => $property ) { @@ -375,7 +388,7 @@ public function prepare_item_for_response( $sidebar, $request ) { foreach ( $sidebar['widgets'] as $widget_id => $widget ) { $widget_data = array(); - foreach ( $schema['properties']['widgets']['items'] as $property_id => $property ) { + foreach ( $schema['properties']['widgets']['items']['properties'] as $property_id => $property ) { if ( isset( $widget[ $property_id ] ) && gettype( $widget[ $property_id ] ) === $property['type'] ) { $widget_data[ $property_id ] = $widget[ $property_id ]; } elseif ( 'settings' === $property_id && 'array' === gettype( $widget[ $property_id ] ) ) { @@ -396,10 +409,10 @@ public function prepare_item_for_response( $sidebar, $request ) { * returned. * * @param WP_REST_Response $response The response object. - * @param object $sidebar The original status object. - * @param WP_REST_Request $request Request used to generate the response. + * @param object $sidebar The original status object. + * @param WP_REST_Request $request Request used to generate the response. */ - return apply_filters( 'rest_prepare_menu_location', $response, $sidebar, $request ); + return apply_filters( 'rest_prepare_sidebar', $response, $sidebar, $request ); } /** @@ -450,47 +463,50 @@ public function get_item_schema() { 'description' => __( 'Nested widgets.', 'gutenberg' ), 'type' => 'array', 'items' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the widget.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'id_base' => array( - 'description' => __( 'Type of widget for the object.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'widget_class' => array( - 'description' => __( 'Class name of the widget implementation.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'name' => array( - 'description' => __( 'Name of the widget.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'description' => array( - 'description' => __( 'Description of the widget.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'number' => array( - 'description' => __( 'Number of the widget.', 'gutenberg' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'rendered' => array( - 'description' => __( 'HTML representation of the widget.', 'gutenberg' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - 'readonly' => true, - ), - 'settings' => array( - 'description' => __( 'Settings of the widget.', 'gutenberg' ), - 'type' => 'object', - 'context' => array( 'view', 'edit', 'embed' ), - 'default' => array(), + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'id_base' => array( + 'description' => __( 'Type of widget for the object.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'widget_class' => array( + 'description' => __( 'Class name of the widget implementation.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'name' => array( + 'description' => __( 'Name of the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'Description of the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'number' => array( + 'description' => __( 'Number of the widget.', 'gutenberg' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'rendered' => array( + 'description' => __( 'HTML representation of the widget.', 'gutenberg' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'settings' => array( + 'description' => __( 'Settings of the widget.', 'gutenberg' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'default' => array(), + ), ), ), 'default' => array(), @@ -507,11 +523,11 @@ public function get_item_schema() { /** * Retrieves a widget instance. * - * @since 5.7.0 - * * @param array $sidebar sidebar data available at $wp_registered_sidebars. * @param string $id Identifier of the widget instance. + * * @return array Array containing the widget instance. + * @since 5.7.0 */ public static function get_sidebar_widget_instance( $sidebar, $id ) { list( $object, $number, $name ) = static::get_widget_info( $id ); @@ -537,11 +553,11 @@ public static function get_sidebar_widget_instance( $sidebar, $id ) { * * Returning false will effectively short-circuit display of the widget. * - * @since 2.8.0 + * @param array $instance The current widget instance's settings. + * @param WP_Widget $this The current widget instance. + * @param array $args An array of default widget arguments. * - * @param array $instance The current widget instance's settings. - * @param WP_Widget $this The current widget instance. - * @param array $args An array of default widget arguments. + * @since 2.8.0 */ $instance = apply_filters( 'widget_display_callback', $instance, $object, $args ); @@ -555,10 +571,10 @@ public static function get_sidebar_widget_instance( $sidebar, $id ) { /** * Given a widget id returns an array containing information about the widget. * - * @since 5.7.0 - * * @param string $widget_id Identifier of the widget. + * * @return array Array containing the the widget object, the number, and the name. + * @since 5.7.0 */ private static function get_widget_info( $widget_id ) { global $wp_registered_widgets; @@ -575,6 +591,7 @@ private static function get_widget_info( $widget_id ) { $object = $wp_registered_widgets[ $widget_id ]['callback'][0]; $number = $wp_registered_widgets[ $widget_id ]['params'][0]['number']; $name = $wp_registered_widgets[ $widget_id ]['name']; + return array( $object, $number, $name ); } diff --git a/lib/class-wp-rest-widget-utils-controller.php b/lib/class-wp-rest-widget-utils-controller.php index 7f3853329163b..832a25a625280 100644 --- a/lib/class-wp-rest-widget-utils-controller.php +++ b/lib/class-wp-rest-widget-utils-controller.php @@ -37,13 +37,13 @@ public function register_routes() { '/' . $this->rest_base . '/form/(?P[^/]*)/', array( 'args' => array( - 'widget_class' => array( + 'widget_class' => array( 'description' => __( 'Class name of the widget.', 'gutenberg' ), 'type' => 'string', 'required' => true, 'validate_callback' => array( $this, 'is_valid_widget' ), ), - 'instance' => array( + 'instance' => array( 'description' => __( 'Current widget instance', 'gutenberg' ), 'type' => 'object', 'default' => array(), diff --git a/lib/load.php b/lib/load.php index 59ed5e0e838f9..ed97a9c2188db 100644 --- a/lib/load.php +++ b/lib/load.php @@ -17,7 +17,6 @@ * @param string $name The name of the experiment. * * @return bool True when the experiment is enabled. - * */ function gutenberg_is_experiment_enabled( $name ) { $experiments = get_option( 'gutenberg-experiments' ); diff --git a/lib/widgets-page.php b/lib/widgets-page.php index 61fdfca640d7d..3b6c8e96bb6a7 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -36,7 +36,7 @@ class="blocks-widgets-container * @param string $hook Page. */ function gutenberg_widgets_init( $hook ) { - if ( ! in_array( $hook, array( 'gutenberg_page_gutenberg-widgets', 'gutenberg_customizer', 'widgets.php' ) ) ) { + if ( ! in_array( $hook, array( 'gutenberg_page_gutenberg-widgets', 'gutenberg_customizer', 'widgets.php' ), true ) ) { return; } diff --git a/phpunit/class-rest-sidebars-controller-test.php b/phpunit/class-rest-sidebars-controller-test.php new file mode 100644 index 0000000000000..8aff0d33a4e86 --- /dev/null +++ b/phpunit/class-rest-sidebars-controller-test.php @@ -0,0 +1,410 @@ +register_routes(); + } + ); + } + + /** + * Create fake data before our tests run. + * + * @param WP_UnitTest_Factory $factory Helper that lets us create fake data. + */ + public static function wpSetUpBeforeClass( $factory ) { + self::$admin_id = $factory->user->create( + array( + 'role' => 'administrator', + ) + ); + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + self::$subscriber_id = $factory->user->create( + array( + 'role' => 'subscriber', + ) + ); + } + + /** + * + */ + public function setUp() { + parent::setUp(); + + wp_set_current_user( self::$admin_id ); + + // Unregister all widgets and sidebars. + global $wp_registered_sidebars, $_wp_sidebars_widgets; + $wp_registered_sidebars = array(); + $_wp_sidebars_widgets = array(); + update_option( 'sidebars_widgets', array() ); + } + + private function setup_widget( $option_name, $number, $settings ) { + update_option( + $option_name, + array( + $number => $settings, + ) + ); + } + + private function setup_sidebar( $id, $attrs = array(), $widgets = array() ) { + global $wp_registered_sidebars; + update_option( + 'sidebars_widgets', + array( + $id => $widgets, + ) + ); + $wp_registered_sidebars[ $id ] = array_merge( + array( + 'id' => $id, + 'before_widget' => '', + 'after_widget' => '', + 'before_title' => '', + 'after_title' => '', + ), + $attrs + ); + + global $wp_registered_widgets; + foreach ( $wp_registered_widgets as $wp_registered_widget ) { + if ( is_array( $wp_registered_widget['callback'] ) ) { + $wp_registered_widget['callback'][0]->_register(); + } + } + } + + /** + * + */ + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( '/__experimental/sidebars', $routes ); + $this->assertArrayHasKey( '/__experimental/sidebars/(?P[\w-]+)', $routes ); + } + + /** + * + */ + public function test_context_param() { + } + + /** + * + */ + public function test_get_items() { + $request = new WP_REST_Request( 'GET', '/__experimental/sidebars' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( array(), $data ); + } + + /** + * + */ + public function test_get_items_basic_sidebar() { + $this->setup_sidebar( + 'sidebar-1', + array( + 'name' => 'Test sidebar', + ) + ); + + $request = new WP_REST_Request( 'GET', '/__experimental/sidebars' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( + array( + array( + 'id' => 'sidebar-1', + 'name' => 'Test sidebar', + 'description' => '', + 'status' => 'active', + 'widgets' => array(), + ), + ), + $data + ); + } + + /** + * + */ + public function test_get_items_active_sidebar_with_widgets() { + $this->setup_widget( + 'widget_rss', + 1, + array( + 'title' => 'RSS test', + ) + ); + $this->setup_widget( + 'widget_text', + 1, + array( + 'text' => 'Custom text test', + ) + ); + $this->setup_sidebar( + 'sidebar-1', + array( + 'name' => 'Test sidebar', + ), + array( 'text-1', 'rss-1' ) + ); + + $request = new WP_REST_Request( 'GET', '/__experimental/sidebars' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( + array( + array( + 'id' => 'sidebar-1', + 'name' => 'Test sidebar', + 'description' => '', + 'status' => 'active', + 'widgets' => array( + array( + 'id' => 'text-1', + 'settings' => array( + 'text' => 'Custom text test', + ), + 'id_base' => 'text', + 'widget_class' => 'WP_Widget_Text', + 'name' => 'Text', + 'description' => 'Arbitrary text.', + 'number' => 1, + 'rendered' => '
Custom text test
' . "\n ", + ), + array( + 'id' => 'rss-1', + 'settings' => array( + 'title' => 'RSS test', + ), + 'id_base' => 'rss', + 'widget_class' => 'WP_Widget_RSS', + 'name' => 'RSS', + 'description' => 'Entries from any RSS or Atom feed.', + 'number' => 1, + 'rendered' => '', + ), + ), + ), + ), + $data + ); + } + + /** + * + */ + public function test_get_item() { + $this->setup_sidebar( + 'sidebar-1', + array( + 'name' => 'Test sidebar', + ) + ); + + $request = new WP_REST_Request( 'GET', '/__experimental/sidebars/sidebar-1' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( + array( + 'id' => 'sidebar-1', + 'name' => 'Test sidebar', + 'description' => '', + 'status' => 'active', + 'widgets' => array(), + ), + $data + ); + } + + /** + * The test_update_item() method does not exist for sidebar. + */ + public function test_create_item() { + } + + /** + * + */ + public function test_update_item() { + $this->setup_widget( + 'widget_rss', + 1, + array( + 'title' => 'RSS test', + ) + ); + $this->setup_widget( + 'widget_text', + 1, + array( + 'text' => 'Custom text test', + ) + ); + $this->setup_sidebar( + 'sidebar-1', + array( + 'name' => 'Test sidebar', + ), + array( 'text-1', 'rss-1' ) + ); + + $request = new WP_REST_Request( 'POST', '/__experimental/sidebars/sidebar-1' ); + $request->set_body_params( + array( + 'widgets' => array( + array( + 'id' => 'text-1', + 'settings' => array( + 'text' => 'Updated text test', + ), + 'id_base' => 'text', + 'widget_class' => 'WP_Widget_Text', + 'name' => 'Text', + 'description' => 'Arbitrary text.', + 'number' => 1, + ), + array( + 'id' => 'text-2', + 'settings' => array( + 'text' => 'Another text widget', + ), + 'id_base' => 'text', + 'widget_class' => 'WP_Widget_Text', + 'name' => 'Text', + 'description' => 'Arbitrary text.', + 'number' => 2, + ), + ), + ) + ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( + array( + 'id' => 'sidebar-1', + 'name' => 'Test sidebar', + 'description' => '', + 'status' => 'active', + 'widgets' => array( + array( + 'id' => 'text-1', + 'settings' => array( + 'text' => 'Updated text test', + 'title' => '', + 'filter' => false, + ), + 'id_base' => 'text', + 'widget_class' => 'WP_Widget_Text', + 'name' => 'Text', + 'description' => 'Arbitrary text.', + 'number' => 1, + 'rendered' => '
Updated text test
' . "\n ", + ), + array( + 'id' => 'text-2', + 'settings' => array( + 'text' => 'Another text widget', + 'title' => '', + 'filter' => false, + ), + 'id_base' => 'text', + 'widget_class' => 'WP_Widget_Text', + 'name' => 'Text', + 'description' => 'Arbitrary text.', + 'number' => 2, + 'rendered' => '
Another text widget
' . "\n ", + ), + ), + ), + $data + ); + } + + /** + * The test_delete_item() method does not exist for sidebar. + */ + public function test_delete_item() { + } + + /** + * The test_prepare_item() method does not exist for sidebar. + */ + public function test_prepare_item() { + } + + /** + * + */ + public function test_get_item_schema() { + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'OPTIONS', '/__experimental/sidebars' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $properties = $data['schema']['properties']; + + $this->assertEquals( 5, count( $properties ) ); + $this->assertArrayHasKey( 'id', $properties ); + $this->assertArrayHasKey( 'name', $properties ); + $this->assertArrayHasKey( 'description', $properties ); + $this->assertArrayHasKey( 'status', $properties ); + $this->assertArrayHasKey( 'widgets', $properties ); + } +} From 4dc08e06a1a5ed2ca454a3e4157f9f64a5fba161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Fri, 7 Aug 2020 16:30:01 +0200 Subject: [PATCH 41/41] Fix indexing bug --- lib/class-wp-rest-sidebars-controller.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-rest-sidebars-controller.php b/lib/class-wp-rest-sidebars-controller.php index 795e68951d20c..900df70340c17 100644 --- a/lib/class-wp-rest-sidebars-controller.php +++ b/lib/class-wp-rest-sidebars-controller.php @@ -209,11 +209,10 @@ public function update_item( $request ) { public function get_items( $request ) { $data = array(); foreach ( (array) wp_get_sidebars_widgets() as $id => $widgets ) { - $sidebar = self::get_sidebar( $id )[0]; + $sidebar = self::get_sidebar( $id )[1]; $data[] = $this->prepare_item_for_response( $sidebar, $request )->get_data(); } - return rest_ensure_response( $data ); } @@ -225,7 +224,7 @@ public function get_items( $request ) { * @return WP_REST_Response */ public function get_item( $request ) { - $sidebar = self::get_sidebar( $request['id'] )[0]; + $sidebar = self::get_sidebar( $request['id'] )[1]; return $this->prepare_item_for_response( $sidebar, $request ); }