diff --git a/src/wp-admin/widgets-form-blocks.php b/src/wp-admin/widgets-form-blocks.php index 1cb1a15e1d59c..c6cb6624e8ee2 100644 --- a/src/wp-admin/widgets-form-blocks.php +++ b/src/wp-admin/widgets-form-blocks.php @@ -24,44 +24,7 @@ ); block_editor_rest_api_preload( $preload_paths, $block_editor_context ); -$editor_settings = get_block_editor_settings( - array( - /** - * Filters the list of widget-type IDs that should **not** be offered by the - * Legacy Widget block. - * - * Returning an empty array will make all widgets available. - * - * @since 5.8.0 - * - * @param array $widgets An array of excluded widget-type IDs. - */ - 'widgetTypesToHideFromLegacyWidgetBlock' => apply_filters( - 'widget_types_to_hide_from_legacy_widget_block', - array( - 'pages', - 'calendar', - 'archives', - 'media_audio', - 'media_image', - 'media_gallery', - 'media_video', - 'meta', - 'search', - 'text', - 'categories', - 'recent-posts', - 'recent-comments', - 'rss', - 'tag_cloud', - 'nav_menu', - 'custom_html', - 'block', - ) - ), - ), - $block_editor_context -); +$editor_settings = get_block_editor_settings( array(), $block_editor_context ); wp_add_inline_script( 'wp-edit-widgets', diff --git a/src/wp-admin/widgets.php b/src/wp-admin/widgets.php index 1258b7049dce2..303ebb0bc97f1 100644 --- a/src/wp-admin/widgets.php +++ b/src/wp-admin/widgets.php @@ -23,21 +23,7 @@ $title = __( 'Widgets' ); $parent_file = 'themes.php'; -/** - * Filters whether or not to use the block editor to manage widgets. - * - * @since 5.8.0 - * - * @param boolean $use_widgets_block_editor Whether or not to use the block editor to manage widgets. - */ -$use_widgets_block_editor = apply_filters( - 'use_widgets_block_editor', - get_theme_support( 'widgets-block-editor' ) -); - -$use_widgets_block_editor = true; - -if ( $use_widgets_block_editor ) { +if ( wp_use_widgets_block_editor() ) { require ABSPATH . 'wp-admin/widgets-form-blocks.php'; } else { require ABSPATH . 'wp-admin/widgets-form.php'; diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 8a110ccbdadbf..0fb57a280dba6 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -225,6 +225,40 @@ function get_default_block_editor_settings() { $editor_settings['gradients'] = $gradient_presets; } + /** + * Filters the list of widget-type IDs that should **not** be offered by the + * Legacy Widget block. + * + * Returning an empty array will make all widgets available. + * + * @since 5.8.0 + * + * @param array $widgets An array of excluded widget-type IDs. + */ + $editor_settings['widgetTypesToHideFromLegacyWidgetBlock'] = apply_filters( + 'widget_types_to_hide_from_legacy_widget_block', + array( + 'pages', + 'calendar', + 'archives', + 'media_audio', + 'media_image', + 'media_gallery', + 'media_video', + 'meta', + 'search', + 'text', + 'categories', + 'recent-posts', + 'recent-comments', + 'rss', + 'tag_cloud', + 'nav_menu', + 'custom_html', + 'block', + ) + ); + return $editor_settings; } diff --git a/src/wp-includes/class-wp-customize-control.php b/src/wp-includes/class-wp-customize-control.php index 25964fb7bc7dd..04db0d4e4c808 100644 --- a/src/wp-includes/class-wp-customize-control.php +++ b/src/wp-includes/class-wp-customize-control.php @@ -795,3 +795,8 @@ protected function content_template() {} * WP_Customize_Date_Time_Control class. */ require_once ABSPATH . WPINC . '/customize/class-wp-customize-date-time-control.php'; + +/** + * WP_Sidebar_Block_Editor_Control class. + */ +require_once ABSPATH . WPINC . '/customize/class-wp-sidebar-block-editor-control.php'; diff --git a/src/wp-includes/class-wp-customize-widgets.php b/src/wp-includes/class-wp-customize-widgets.php index ed9bbe3dfd5e7..b2ab25601d284 100644 --- a/src/wp-includes/class-wp-customize-widgets.php +++ b/src/wp-includes/class-wp-customize-widgets.php @@ -118,6 +118,7 @@ public function __construct( $manager ) { add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_widget_control_templates' ) ); add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) ); add_filter( 'customize_refresh_nonces', array( $this, 'refresh_nonces' ) ); + add_filter( 'should_load_block_editor_scripts_and_styles', array( $this, 'should_load_block_editor_scripts_and_styles' ) ); add_action( 'dynamic_sidebar', array( $this, 'tally_rendered_widgets' ) ); add_filter( 'is_active_sidebar', array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 ); @@ -368,6 +369,8 @@ public function schedule_customize_register() { public function customize_register() { global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_sidebars; + $use_widgets_block_editor = wp_use_widgets_block_editor(); + add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 ); $sidebars_widgets = array_merge( @@ -446,13 +449,18 @@ public function customize_register() { if ( $is_active_sidebar ) { $section_args = array( - 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'], - 'description' => $wp_registered_sidebars[ $sidebar_id ]['description'], - 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ), true ), - 'panel' => 'widgets', - 'sidebar_id' => $sidebar_id, + 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'], + 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ), true ), + 'panel' => 'widgets', + 'sidebar_id' => $sidebar_id, ); + if ( $use_widgets_block_editor ) { + $section_args['description'] = ''; + } else { + $section_args['description'] = $wp_registered_sidebars[ $sidebar_id ]['description']; + } + /** * Filters Customizer widget section arguments for a given sidebar. * @@ -467,49 +475,63 @@ public function customize_register() { $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args ); $this->manager->add_section( $section ); - $control = new WP_Widget_Area_Customize_Control( - $this->manager, - $setting_id, - array( - 'section' => $section_id, - 'sidebar_id' => $sidebar_id, - 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. - ) - ); - $new_setting_ids[] = $setting_id; + if ( $use_widgets_block_editor ) { + $control = new WP_Sidebar_Block_Editor_Control( + $this->manager, + $setting_id, + array( + 'section' => $section_id, + 'sidebar_id' => $sidebar_id, + ) + ); + } else { + $control = new WP_Widget_Area_Customize_Control( + $this->manager, + $setting_id, + array( + 'section' => $section_id, + 'sidebar_id' => $sidebar_id, + 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end. + ) + ); + } $this->manager->add_control( $control ); + + $new_setting_ids[] = $setting_id; } } - // Add a control for each active widget (located in a sidebar). - foreach ( $sidebar_widget_ids as $i => $widget_id ) { + if ( ! $use_widgets_block_editor ) { + // Add a control for each active widget (located in a sidebar). + foreach ( $sidebar_widget_ids as $i => $widget_id ) { - // Skip widgets that may have gone away due to a plugin being deactivated. - if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[ $widget_id ] ) ) { - continue; - } + // Skip widgets that may have gone away due to a plugin being deactivated. + if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[ $widget_id ] ) ) { + continue; + } - $registered_widget = $wp_registered_widgets[ $widget_id ]; - $setting_id = $this->get_setting_id( $widget_id ); - $id_base = $wp_registered_widget_controls[ $widget_id ]['id_base']; + $registered_widget = $wp_registered_widgets[ $widget_id ]; + $setting_id = $this->get_setting_id( $widget_id ); + $id_base = $wp_registered_widget_controls[ $widget_id ]['id_base']; - $control = new WP_Widget_Form_Customize_Control( - $this->manager, - $setting_id, - array( - 'label' => $registered_widget['name'], - 'section' => $section_id, - 'sidebar_id' => $sidebar_id, - 'widget_id' => $widget_id, - 'widget_id_base' => $id_base, - 'priority' => $i, - 'width' => $wp_registered_widget_controls[ $widget_id ]['width'], - 'height' => $wp_registered_widget_controls[ $widget_id ]['height'], - 'is_wide' => $this->is_wide_widget( $widget_id ), - ) - ); - $this->manager->add_control( $control ); + $control = new WP_Widget_Form_Customize_Control( + $this->manager, + $setting_id, + array( + 'label' => $registered_widget['name'], + 'section' => $section_id, + 'sidebar_id' => $sidebar_id, + 'widget_id' => $widget_id, + 'widget_id_base' => $id_base, + 'priority' => $i, + 'width' => $wp_registered_widget_controls[ $widget_id ]['width'], + 'height' => $wp_registered_widget_controls[ $widget_id ]['height'], + 'is_wide' => $this->is_wide_widget( $widget_id ), + ) + ); + $this->manager->add_control( $control ); + } } } @@ -805,6 +827,46 @@ public function enqueue_scripts() { 'data', sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) ) ); + + // TODO: Update 'wp-customize-widgets' to not rely so much on things in + // 'customize-widgets'. This will let us skip most of the above and not + // enqueue 'customize-widgets' which saves bytes. + + if ( wp_use_widgets_block_editor() ) { + $block_editor_context = new WP_Block_Editor_Context(); + + $editor_settings = get_block_editor_settings( array(), $block_editor_context ); + + wp_add_inline_script( + 'wp-customize-widgets', + sprintf( + 'wp.domReady( function() { + wp.customizeWidgets.initialize( "widgets-customizer", %s ); + } );', + wp_json_encode( $editor_settings ) + ) + ); + + // Preload server-registered block schemas. + wp_add_inline_script( + 'wp-blocks', + 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');' + ); + + wp_add_inline_script( + 'wp-blocks', + sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( 'widgets-customizer' ) ) ), + 'after' + ); + + wp_enqueue_script( 'wp-customize-widgets' ); + wp_enqueue_style( 'wp-customize-widgets' ); + wp_enqueue_script( 'wp-format-library' ); + wp_enqueue_style( 'wp-format-library' ); + + /** This action is documented in edit-form-blocks.php */ + do_action( 'enqueue_block_editor_assets' ); + } } /** @@ -888,8 +950,13 @@ public function get_setting_args( $id, $overrides = array() ) { $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' ); $args['transport'] = current_theme_supports( 'customize-selective-refresh-widgets' ) ? 'postMessage' : 'refresh'; } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) { - $args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' ); - $args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' ); + $id_base = $matches['id_base']; + $args['sanitize_callback'] = function( $value ) use ( $id_base ) { + return $this->sanitize_widget_instance( $value, $id_base ); + }; + $args['sanitize_js_callback'] = function( $value ) use ( $id_base ) { + return $this->sanitize_widget_js_instance( $value, $id_base ); + }; $args['transport'] = $this->is_widget_selective_refreshable( $matches['id_base'] ) ? 'postMessage' : 'refresh'; } @@ -1108,6 +1175,23 @@ public function refresh_nonces( $nonces ) { return $nonces; } + /** + * Tells the script loader to load the scripts and styles of custom blocks + * if the widgets block editor is enabled. + * + * @since 5.8.0 + * + * @param bool $is_block_editor_screen Current decision about loading block assets. + * @return bool Filtered decision about loading block assets. + */ + public function should_load_block_editor_scripts_and_styles( $is_block_editor_screen ) { + if ( wp_use_widgets_block_editor() ) { + return true; + } + + return $is_block_editor_screen; + } + /** * When previewing, ensures the proper previewing widgets are used. * @@ -1314,16 +1398,28 @@ protected function get_instance_hash_key( $serialized_instance ) { * @since 3.9.0 * * @param array $value Widget instance to sanitize. + * @param string $id_base Base of the ID of the widget being sanitized. * @return array|void Sanitized widget instance. */ - public function sanitize_widget_instance( $value ) { + public function sanitize_widget_instance( $value, $id_base = null ) { + global $wp_widget_factory; + if ( array() === $value ) { - return $value; + return; } - if ( empty( $value['is_widget_customizer_js_value'] ) - || empty( $value['instance_hash_key'] ) - || empty( $value['encoded_serialized_instance'] ) ) { + if ( isset( $value['raw_instance'] ) && $id_base && wp_use_widgets_block_editor() ) { + $widget_object = $wp_widget_factory->get_widget_object( $id_base ); + if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { + return $value['raw_instance']; + } + } + + if ( + empty( $value['is_widget_customizer_js_value'] ) || + empty( $value['instance_hash_key'] ) || + empty( $value['encoded_serialized_instance'] ) + ) { return; } @@ -1350,19 +1446,32 @@ public function sanitize_widget_instance( $value ) { * @since 3.9.0 * * @param array $value Widget instance to convert to JSON. + * @param string $id_base Base of the ID of the widget being sanitized. * @return array JSON-converted widget instance. */ - public function sanitize_widget_js_instance( $value ) { + public function sanitize_widget_js_instance( $value, $id_base = null ) { + global $wp_widget_factory; + if ( empty( $value['is_widget_customizer_js_value'] ) ) { $serialized = serialize( $value ); - $value = array( + $js_value = array( 'encoded_serialized_instance' => base64_encode( $serialized ), 'title' => empty( $value['title'] ) ? '' : $value['title'], 'is_widget_customizer_js_value' => true, 'instance_hash_key' => $this->get_instance_hash_key( $serialized ), ); + + if ( $id_base && wp_use_widgets_block_editor() ) { + $widget_object = $wp_widget_factory->get_widget_object( $id_base ); + if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { + $js_value['raw_instance'] = (object) $value; + } + } + + return $js_value; } + return $value; } @@ -1432,7 +1541,7 @@ public function call_widget_update( $widget_id ) { return new WP_Error( 'widget_setting_malformed' ); } - $instance = $this->sanitize_widget_instance( $sanitized_widget_setting ); + $instance = $this->sanitize_widget_instance( $sanitized_widget_setting, $parsed_id['id_base'] ); if ( is_null( $instance ) ) { $this->stop_capturing_option_updates(); return new WP_Error( 'widget_setting_unsanitized' ); @@ -1498,7 +1607,7 @@ public function call_widget_update( $widget_id ) { * in place from WP_Customize_Setting::preview() will use this value * instead of the default widget instance value (an empty array). */ - $this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance ) ); + $this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance, $parsed_id['id_base'] ) ); // Obtain the widget control with the updated instance in place. ob_start(); @@ -1571,7 +1680,7 @@ public function wp_ajax_update_widget() { } $form = $updated_widget['form']; - $instance = $this->sanitize_widget_js_instance( $updated_widget['instance'] ); + $instance = $this->sanitize_widget_js_instance( $updated_widget['instance'], $id_base ); wp_send_json_success( compact( 'form', 'instance' ) ); } diff --git a/src/wp-includes/customize/class-wp-sidebar-block-editor-control.php b/src/wp-includes/customize/class-wp-sidebar-block-editor-control.php new file mode 100644 index 0000000000000..a2d1e4bc78b1b --- /dev/null +++ b/src/wp-includes/customize/class-wp-sidebar-block-editor-control.php @@ -0,0 +1,37 @@ + array( + 'wp-components', + 'wp-block-editor', + 'wp-edit-blocks', + 'wp-block-library', + 'wp-reusable-blocks', ), ); diff --git a/src/wp-includes/widgets.php b/src/wp-includes/widgets.php index c044215e07eef..ba6aea7a6ba34 100644 --- a/src/wp-includes/widgets.php +++ b/src/wp-includes/widgets.php @@ -1801,6 +1801,8 @@ function wp_widgets_init() { register_widget( 'WP_Widget_Block' ); + add_theme_support( 'widgets-block-editor' ); + /** * Fires after all default WordPress widgets have been registered. * @@ -1809,6 +1811,27 @@ function wp_widgets_init() { do_action( 'widgets_init' ); } +/** + * Whether or not to use the block editor to manage widgets. Defaults to true + * unless a theme has removed support for widgets-block-editor or a plugin has + * filtered the return value of this function. + * + * @since 5.8.0 + * + * @return boolean Whether or not to use the block editor to manage widgets. + */ +function wp_use_widgets_block_editor() { + /** + * Filters whether or not to use the block editor to manage widgets. + * + * @param boolean $use_widgets_block_editor Whether or not to use the block editor to manage widgets. + */ + return apply_filters( + 'use_widgets_block_editor', + get_theme_support( 'widgets-block-editor' ) + ); +} + /** * Converts a widget ID into its id_base and number components. * diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php index 7e409d5fce1ad..b8274c0a115d1 100644 --- a/tests/phpunit/includes/functions.php +++ b/tests/phpunit/includes/functions.php @@ -310,6 +310,7 @@ function _unhook_block_registration() { remove_action( 'init', 'register_block_core_loginout' ); remove_action( 'init', 'register_block_core_latest_comments' ); remove_action( 'init', 'register_block_core_latest_posts' ); + remove_action( 'init', 'register_block_core_legacy_widget', 20 ); remove_action( 'init', 'register_block_core_post_author' ); remove_action( 'init', 'register_block_core_post_content' ); remove_action( 'init', 'register_block_core_post_date' ); diff --git a/tests/phpunit/tests/blocks/block-editor.php b/tests/phpunit/tests/blocks/block-editor.php index b11ac707c8933..896a1ae36fd05 100644 --- a/tests/phpunit/tests/blocks/block-editor.php +++ b/tests/phpunit/tests/blocks/block-editor.php @@ -159,7 +159,7 @@ function test_get_allowed_block_types_deprecated_filter_post_editor() { function test_get_default_block_editor_settings() { $settings = get_default_block_editor_settings(); - $this->assertCount( 16, $settings ); + $this->assertCount( 17, $settings ); $this->assertFalse( $settings['alignWide'] ); $this->assertInternalType( 'array', $settings['allowedMimeTypes'] ); $this->assertTrue( $settings['allowedBlockTypes'] ); @@ -254,6 +254,29 @@ function test_get_default_block_editor_settings() { $settings['imageSizes'] ); $this->assertInternalType( 'int', $settings['maxUploadFileSize'] ); + $this->assertSameSets( + array( + 'archives', + 'block', + 'calendar', + 'categories', + 'custom_html', + 'media_audio', + 'media_gallery', + 'media_image', + 'media_video', + 'meta', + 'nav_menu', + 'pages', + 'recent-comments', + 'recent-posts', + 'rss', + 'search', + 'tag_cloud', + 'text', + ), + $settings['widgetTypesToHideFromLegacyWidgetBlock'] + ); } /** diff --git a/tests/phpunit/tests/customize/widgets.php b/tests/phpunit/tests/customize/widgets.php index 46ab3f81b5da7..19bf9772be3c8 100644 --- a/tests/phpunit/tests/customize/widgets.php +++ b/tests/phpunit/tests/customize/widgets.php @@ -25,6 +25,7 @@ function setUp() { require_once ABSPATH . WPINC . '/class-wp-customize-manager.php'; add_theme_support( 'customize-selective-refresh-widgets' ); + add_action( 'widgets_init', array( $this, 'remove_widgets_block_editor' ) ); $user_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $user_id ); $GLOBALS['wp_customize'] = new WP_Customize_Manager(); @@ -85,6 +86,10 @@ function do_customize_boot_actions() { do_action( 'wp', $GLOBALS['wp'] ); } + function remove_widgets_block_editor() { + remove_theme_support( 'widgets-block-editor' ); + } + /** * Test WP_Customize_Widgets::__construct() */ @@ -258,31 +263,31 @@ function test_get_setting_args() { add_filter( 'widget_customizer_setting_args', array( $this, 'filter_widget_customizer_setting_args' ), 10, 2 ); $default_args = array( - 'type' => 'option', - 'capability' => 'edit_theme_options', - 'transport' => 'refresh', - 'default' => array(), - 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_widget_instance' ), - 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_widget_js_instance' ), + 'type' => 'option', + 'capability' => 'edit_theme_options', + 'transport' => 'refresh', + 'default' => array(), ); $args = $this->manager->widgets->get_setting_args( 'widget_foo[2]' ); foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } + $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); + $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); $this->assertSame( 'WIDGET_FOO[2]', $args['uppercase_id_set_by_filter'] ); $default_args = array( - 'type' => 'option', - 'capability' => 'edit_theme_options', - 'transport' => 'postMessage', - 'default' => array(), - 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_widget_instance' ), - 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_widget_js_instance' ), + 'type' => 'option', + 'capability' => 'edit_theme_options', + 'transport' => 'postMessage', + 'default' => array(), ); $args = $this->manager->widgets->get_setting_args( 'widget_search[2]' ); foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } + $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); + $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); remove_theme_support( 'customize-selective-refresh-widgets' ); $args = $this->manager->widgets->get_setting_args( 'widget_search[2]' ); @@ -304,17 +309,17 @@ function test_get_setting_args() { $this->assertSame( 'WIDGET_BAR[3]', $args['uppercase_id_set_by_filter'] ); $default_args = array( - 'type' => 'option', - 'capability' => 'edit_theme_options', - 'transport' => 'postMessage', - 'default' => array(), - 'sanitize_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets' ), - 'sanitize_js_callback' => array( $this->manager->widgets, 'sanitize_sidebar_widgets_js_instance' ), + 'type' => 'option', + 'capability' => 'edit_theme_options', + 'transport' => 'postMessage', + 'default' => array(), ); $args = $this->manager->widgets->get_setting_args( 'sidebars_widgets[sidebar-1]' ); foreach ( $default_args as $key => $default_value ) { $this->assertSame( $default_value, $args[ $key ] ); } + $this->assertTrue( is_callable( $args['sanitize_callback'] ), 'sanitize_callback is callable' ); + $this->asserttrue( is_callable( $args['sanitize_js_callback'] ), 'sanitize_js_callback is callable' ); $this->assertSame( 'SIDEBARS_WIDGETS[SIDEBAR-1]', $args['uppercase_id_set_by_filter'] ); $override_args = array(