Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizer: Add widget blocks section. #16204

Merged
merged 16 commits into from
Jul 4, 2019
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions lib/class-experimental-wp-widget-blocks-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ public static function output_blocks_widget( $options, $arguments ) {
}

/**
* Registers of a widget that should represent a set of blocks and returns its id.
* Registers a widget that should represent a set of blocks and returns its ID.
*
* @param array $blocks Array of blocks.
* @param array $blocks Array of blocks.
*/
public static function convert_blocks_to_widget( $blocks ) {
$widget_id = 'blocks-widget-' . md5( self::serialize_blocks( $blocks ) );
Expand All @@ -320,7 +320,7 @@ public static function convert_blocks_to_widget( $blocks ) {
}
wp_register_sidebar_widget(
$widget_id,
__( 'Blocks Area ', 'gutenberg' ),
__( 'Blocks Area', 'gutenberg' ),
'Experimental_WP_Widget_Blocks_Manager::output_blocks_widget',
array(
'classname' => 'widget-area',
Expand All @@ -330,6 +330,12 @@ public static function convert_blocks_to_widget( $blocks ) {
'blocks' => $blocks,
)
);
wp_register_widget_control(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the commit, it seems like this was added to avoid missing widget control errors. The control is the edition interface of a widget, and it looks like widget controls are not mandatory, e.g., I can have a print hello widget, without any UI to edit it.
It would be interesting to check if the warnings also happen on other widgets without a control associated as it may be an existing core bug or it may be another problem in our Blocks Area Widget.
For now, I guess if this solution fixes the problem it is something we can use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I found it strange too.

$widget_id,
__( 'Blocks Area', 'gutenberg' ),
'echo',
Copy link
Member

@jorgefilipecosta jorgefilipecosta Jul 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still triggers an error:

call_user_func_array() expects parameter 1 to be a valid callback, function 'echo' not found or invalid function name

It seems echo is not considered a callback function, but using a function that prints something would be dangerous anyway as printing something during REST request may make the requests invalid.
It seems our best bet is to create a simple noop function ourselves and pass its name here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do that 👍

array( 'id_base' => 'blocks-widget' )
);
return $widget_id;
}

Expand All @@ -340,20 +346,34 @@ public static function convert_blocks_to_widget( $blocks ) {
*/
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 ) {
if ( ! is_numeric( $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();
$post = get_post( $item );
$blocks = parse_blocks( $post->post_content );
$blocks = parse_blocks(
$changeset_value ? $changeset_value : get_post( $item )->post_content
);

foreach ( $blocks as $block ) {
if ( ! isset( $block['blockName'] ) ) {
Expand All @@ -379,6 +399,7 @@ public static function swap_out_sidebars_blocks_for_block_widgets( $sidebars_wid
$filtered_sidebar_widgets[ $sidebar_id ] = $filtered_widgets;
}
$sidebars_widgets = $filtered_sidebar_widgets;

return $filtered_sidebar_widgets;
}
}
40 changes: 40 additions & 0 deletions lib/class-wp-customize-widget-blocks-control.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
/**
* Customizer Widget Blocks Section: WP_Customize_Widget_Blocks_Control class.
*
* @package gutenberg
* @since 6.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since 6.0.0 was published today, we'll need to push these again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update it once we are ready to merge.

*/

/**
* Class that renders the Customizer control for editing widgets with Gutenberg.
*
* @since 6.0.0
*/
class WP_Customize_Widget_Blocks_Control extends WP_Customize_Control {
/**
* Enqueue control related scripts/styles.
*
* @since 6.0.0
*/
public function enqueue() {
gutenberg_widgets_init( 'gutenberg_customizer' );
}

/**
* Render the control's content.
*
* @since 6.0.0
*/
public function render_content() {
?>
<input
id="_customize-input-gutenberg_widget_blocks"
type="hidden"
value="<?php echo esc_attr( $this->value() ); ?>"
<?php $this->link(); ?>
/>
<?php
the_gutenberg_widgets( 'gutenberg_customizer' );
}
}
120 changes: 120 additions & 0 deletions lib/customizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
/**
* Bootstraping the Gutenberg Customizer widget blocks section.
*
* 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.
*
* @package gutenberg
*/

/**
* The sanitization function for incoming values for the `gutenberg_widget_blocks` setting.
* It's a JSON string, so it decodes it and encodes it again to make sure it's valid.
*
* @param string $value The incoming value.
*/
function gutenberg_customize_sanitize( $value ) {
return json_encode( json_decode( $value ) );
}

/**
* Gutenberg's Customize Register.
*
* Adds a section to the Customizer for editing widgets with Gutenberg.
*
* @param \WP_Customize_Manager $wp_customize An instance of the class that controls most of the Theme Customization API for WordPress 3.4 and newer.
* @since 6.0.0
*/
function gutenberg_customize_register( $wp_customize ) {
require dirname( __FILE__ ) . '/class-wp-customize-widget-blocks-control.php';
$wp_customize->add_setting(
'gutenberg_widget_blocks',
array(
'default' => '{}',
'type' => 'gutenberg_widget_blocks',
'capability' => 'edit_theme_options',
'transport' => 'postMessage',
'sanitize_callback' => 'gutenberg_customize_sanitize',
)
);
$wp_customize->add_section(
'gutenberg_widget_blocks',
array( 'title' => __( 'Widget Blocks (Experimental)', 'gutenberg' ) )
);
$wp_customize->add_control( new WP_Customize_Widget_Blocks_Control(
$wp_customize,
'gutenberg_widget_blocks',
array(
'section' => 'gutenberg_widget_blocks',
'settings' => 'gutenberg_widget_blocks',
)
) );
}
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.
* The `WP_Customize_Widgets` class expects sidebars to have an array of widgets registered, not a post ID.
* This results in the value passed to `sanitize_js_callback` being `null` and throwing an error.
*
* TODO: Figure out why core is not running the `sidebars_widgets` filter for the relevant part of the code.
* Then, either fix it or change this filter to parse the post IDs and then pass them to the original `sanitize_js_callback`.
*
* @param array $args Array of Customizer setting arguments.
* @param string $id Widget setting ID.
* @return array Maybe modified array of Customizer setting arguments.
*/
function filter_widget_customizer_setting_args( $args, $id = null ) {
// Posts won't have a settings ID like widgets. We can use that to remove the sanitization callback.
if ( ! isset( $id ) ) {
unset( $args['sanitize_js_callback'] );
}

return $args;
}
add_filter( 'widget_customizer_setting_args', 'filter_widget_customizer_setting_args' );
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@
require dirname( __FILE__ ) . '/demo.php';
require dirname( __FILE__ ) . '/widgets.php';
require dirname( __FILE__ ) . '/widgets-page.php';
require dirname( __FILE__ ) . '/customizer.php';
28 changes: 24 additions & 4 deletions lib/widgets-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@
* The main entry point for the Gutenberg widgets page.
*
* @since 5.2.0
*
* @param string $page The page name the function is being called for, `'gutenberg_customizer'` for the Customizer.
*/
function the_gutenberg_widgets() {
function the_gutenberg_widgets( $page = 'gutenberg_page_gutenberg-widgets' ) {
?>
<div id="widgets-editor" class="blocks-widgets-container">
<div
id="widgets-editor"
class="blocks-widgets-container
<?php
echo 'gutenberg_customizer' === $page
? ' is-in-customizer'
: '';
?>
"
>
</div>
<?php
}
Expand All @@ -25,10 +36,14 @@ function the_gutenberg_widgets() {
* @param string $hook Page.
*/
function gutenberg_widgets_init( $hook ) {
if ( 'gutenberg_page_gutenberg-widgets' !== $hook ) {
if ( 'gutenberg_page_gutenberg-widgets' !== $hook && 'gutenberg_customizer' !== $hook ) {
return;
}

$initializer_name = 'gutenberg_page_gutenberg-widgets' === $hook
? 'initialize'
: 'customizerInitialize';

// Media settings.
$max_upload_size = wp_max_upload_size();
if ( ! $max_upload_size ) {
Expand Down Expand Up @@ -58,15 +73,20 @@ function gutenberg_widgets_init( $hook ) {
wp_add_inline_script(
'wp-edit-widgets',
sprintf(
'wp.editWidgets.initialize( "widgets-editor", %s );',
'wp.domReady( function() {
wp.editWidgets.%s( "widgets-editor", %s );
} );',
$initializer_name,
wp_json_encode( $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_enqueue_script( 'wp-edit-widgets' );
wp_enqueue_script( 'wp-format-library' );
wp_enqueue_style( 'wp-edit-widgets' );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* WordPress dependencies
*/
import {
SlotFillProvider,
Popover,
navigateRegions,
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import WidgetAreas from '../widget-areas';

import './sync-customizer';

function CustomizerEditWidgetsInitializer( { settings } ) {
return (
<SlotFillProvider>
<div
className="edit-widgets-customizer-edit-widgets-initializer__content"
role="region"
aria-label={ __( 'Widgets screen content' ) }
tabIndex="-1"
>
<WidgetAreas blockEditorSettings={ settings } />
</div>
<Popover.Slot />
</SlotFillProvider>
);
}

export default navigateRegions( CustomizerEditWidgetsInitializer );
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.edit-widgets-customizer-edit-widgets-initializer__content {
background: #f1f1f1;
margin: 0;
min-height: 100%;
padding: 30px 0;

.block-editor-block-list__layout {
padding: 0 0 0 18px;
}

.block-editor-block-list__empty-block-inserter {
left: -18px;
}

.block-editor-rich-text__editable[data-is-placeholder-visible="true"] + .block-editor-rich-text__editable.wp-block-paragraph {
padding: 0;
}
}
Loading