Skip to content

Commit

Permalink
Support storing blocks in sidebars
Browse files Browse the repository at this point in the history
- Allows blocks to be stored in a WordPress sidebar along with widgets.
- Adds a REST endpoint for viewing sidebars and their blocks. Any
  widgets in the sidebar are shown as core/core-legacy blocks.
- Adds a REST endpoint for updating a sidebar's blocks. Any
  core/legacy-widget blocks are stored in a backwards compatible way.
- Block data is serialized directly as an array into the
  'sidebars_widgets' site option.
- We intercept calls to wp_get_sidebars_widgets() and replace any blocks
  with a legacy callback widget. This allows blocks to be rendered via
  dynamic_sidebar(), and ensures that existing code that uses
  wp_get_sidebars_widgets() does not break.
  • Loading branch information
noisysocks committed Mar 6, 2019
1 parent fd35a1b commit bad4cc8
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 2 deletions.
200 changes: 200 additions & 0 deletions lib/class-wp-rest-sidebars-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<?php

class WP_REST_Sidebars_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'sidebars';
}

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' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>.+)',
array(
'args' => array(
'id' => array(
'description' => __( 'The sidebar’s ID.', 'gutenberg' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_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' ),
)
);
}

public function get_items_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;
}

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 );
}

public function get_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;
}

public function get_item( $request ) {
$data = $this->get_sidebar_data( $request['id'] );
return rest_ensure_response( $data );
}

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;
}

public function update_item( $request ) {
$result = $this->update_sidebar_blocks( $request['id'], $request );
if ( is_wp_error( $result ) ) {
return $result;
}

$data = $this->get_sidebar_data( $request['id'] );
return rest_ensure_response( $data );
}

// TODO: Add schema

protected function get_sidebar_data( $sidebar_id ) {
global $wp_registered_sidebars;

if ( ! isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
return new WP_Error(
'rest_sidebar_invalid_id',
__( 'Invalid sidebar ID.', 'gutenberg' ),
array( 'status' => 404 )
);
}

// TODO: How should we format blocks in the REST API? Or should we send down
// HTML so that we're consistent with the /posts and /blocks endpoints?
$blocks = array();

$sidebars_items = gutenberg_get_sidebars_items();
if ( ! empty( $sidebars_items[ $sidebar_id ] ) ) {
foreach ( $sidebars_items[ $sidebar_id ] as $item ) {
if ( is_array( $item ) && isset( $item['blockName'] ) ) {
$blocks[] = array(
'name' => $item['blockName'],
'attributes' => $item['attrs'],
'innerBlocks' => $item['innerBlocks'],
'innerHTML' => $item['innerHTML'],
'innerContent' => $item['innerContent'],
);
} else {
$blocks[] = array(
'name' => 'core/legacy-widget',
'attributes' => array( 'identifier' => $item ),
'innerBlocks' => array(),
'innerHTML' => '',
'innerContent' => array(),
);
}
}
}

return array_merge(
$wp_registered_sidebars[ $sidebar_id ],
array( 'blocks' => $blocks )
);
}

protected function update_sidebar_blocks( $sidebar_id, $request ) {
global $wp_registered_sidebars;

if ( ! isset( $wp_registered_sidebars[ $sidebar_id ] ) ) {
return new WP_Error(
'rest_sidebar_invalid_id',
__( 'Invalid sidebar ID.', 'gutenberg' ),
array( 'status' => 404 )
);
}

$items = array();

if ( isset( $request['blocks'] ) && is_array( $request['blocks'] ) ) {
foreach ( $request['blocks'] as $block ) {
if (
! isset( $block['name'] ) || ! is_string( $block['name'] ) ||
! isset( $block['attributes'] ) || ! is_array( $block['attributes'] ) ||
! isset( $block['innerBlocks'] ) || ! is_array( $block['innerBlocks'] ) ||
! isset( $block['innerHTML'] ) || ! is_string( $block['innerHTML'] ) ||
! isset( $block['innerContent'] ) || ! is_array( $block['innerContent'] )
) {
continue;
}

if ( 'core/legacy-widget' === $block['name'] ) {
$items[] = $block['attributes']['identifier'];
} else {
$items[] = array(
'blockName' => $block['name'],
'attrs' => $block['attributes'],
'innerBlocks' => $block['innerBlocks'],
'innerHTML' => $block['innerHTML'],
'innerContent' => $block['innerContent'],
);
}
}
}

if ( ! empty( $items ) ) {
gutenberg_set_sidebars_items( array_merge(
gutenberg_get_sidebars_items(),
array( $sidebar_id => $items )
) );
}
}
}
4 changes: 4 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
// These files only need to be loaded if within a rest server instance
// which this class will exist if that is the case.
if ( class_exists( 'WP_REST_Controller' ) ) {
if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php';
}

require dirname( __FILE__ ) . '/rest-api.php';
}

Expand Down
83 changes: 83 additions & 0 deletions lib/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,86 @@ function gutenberg_revisions_restore( $revisions_data ) {

return $revisions_data;
}

function gutenberg_output_block_widget( $options, $block ) {
echo $options['before_widget'];
echo render_block( $block );
echo $options['after_widget'];
}

function gutenberg_swap_out_sidebars_blocks_for_block_widgets( $sidebars_items ) {
global $wp_registered_widgets;

foreach ( $sidebars_items as $sidebar_id => $items ) {
foreach ( $items as $index => $item ) {
if ( ! is_array( $item ) || ! isset( $item['blockName'] ) ) {
continue;
}

$widget_id = 'block-widget-' . md5( serialize( $item ) );

$sidebars_items[ $sidebar_id ][ $index ] = $widget_id;

if ( isset( $wp_registered_widgets[ $widget_id ] ) ) {
continue;
}

wp_register_sidebar_widget(
$widget_id,
// TODO: Can we get the block's title somehow?
/* translators: %s: Name of the block */
sprintf( __( 'Block: %s', 'gutenberg' ), $item['blockName'] ),
'gutenberg_output_block_widget',
array(
'classname' => 'block-widget',
'description' => sprintf(
/* translators: %s: Name of the block */
__( 'Displays a ‘%s’ block.', 'gutenberg' ),
$item['blockName']
),
),
$item
);
}
}

return $sidebars_items;
}
add_filter( 'sidebars_widgets', 'gutenberg_swap_out_sidebars_blocks_for_block_widgets' );

function gutenberg_swap_out_sidebars_block_widgets_for_blocks( $sidebars_widgets ) {
global $wp_registered_widgets;

foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
foreach ( $widgets as $index => $widget_id ) {
if ( 0 !== strpos( $widget_id, 'block-widget-' ) ) {
continue;
}

if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
unset( $sidebars_widgets[ $sidebar_id ][ $index ] );
continue;
}

$block = $wp_registered_widgets[ $widget_id ]['params'][0];

$sidebars_widgets[ $sidebar_id ][ $index ] = $block;
}
}

return $sidebars_widgets;
}
add_filter( 'pre_update_option_sidebars_widgets', 'gutenberg_swap_out_sidebars_block_widgets_for_blocks' );

function gutenberg_get_sidebars_items() {
remove_filter( 'sidebars_widgets', 'gutenberg_swap_out_sidebars_blocks_for_block_widgets' );
$sidebars_widgets = wp_get_sidebars_widgets();
add_filter( 'sidebars_widgets', 'gutenberg_swap_out_sidebars_blocks_for_block_widgets' );
return $sidebars_widgets;
}

function gutenberg_set_sidebars_items( $sidebars_items ) {
remove_filter( 'pre_update_option_sidebars_widgets', 'gutenberg_swap_out_sidebars_block_widgets_for_blocks' );
wp_set_sidebars_widgets( $sidebars_items );
add_filter( 'pre_update_option_sidebars_widgets', 'gutenberg_swap_out_sidebars_block_widgets_for_blocks' );
}
5 changes: 3 additions & 2 deletions lib/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
* Registers the REST API routes needed by the Gutenberg editor.
*
* @since 2.8.0
* @deprecated 5.0.0
*/
function gutenberg_register_rest_routes() {
_deprecated_function( __FUNCTION__, '5.0.0' );
$sidebar_controller = new WP_REST_Sidebars_Controller();
$sidebar_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_rest_routes' );

/**
* Handle a failing oEmbed proxy request to try embedding as a shortcode.
Expand Down

0 comments on commit bad4cc8

Please sign in to comment.