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

Support storing blocks in sidebars #14251

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
32 changes: 32 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,35 @@ function gutenberg_reregister_core_block_types() {
}
}
add_action( 'init', 'gutenberg_reregister_core_block_types' );

function serialize_blocks( $blocks ) {
return implode( array_map( 'serialize_block', $blocks ) );
}

function serialize_block( $block ) {
$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(
'<!-- wp:%s%s /-->',
$name,
$opening_tag_suffix
);
} else {
return sprintf(
'<!-- wp:%1$s%2$s -->%3$s<!-- /wp:%1$s -->',
$name,
$opening_tag_suffix,
$block['innerHTML']
);
}
}
282 changes: 282 additions & 0 deletions lib/class-wp-rest-sidebars-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
<?php

class WP_REST_Sidebars_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = '__experimental';
$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 ) {
return rest_ensure_response( $this->get_sidebar_data( $request['id'] ) );
Copy link
Member

Choose a reason for hiding this comment

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

If I'm not in error the good practice to access attributes from a request is using the $request->get_param function e.g:$request->get_param( 'id' ).

}

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 ) {
$status = $this->update_sidebar_data( $request['id'], $request );
if ( is_wp_error( $status ) ) {
return $status;
}

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

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

$sidebar = $wp_registered_sidebars[ $sidebar_id ];
$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[] = $item;
} else {
$blocks[] = array(
'blockName' => 'core/legacy-widget',
'attrs' => array(
'identifier' => $item,
Copy link
Member

Choose a reason for hiding this comment

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

If the widget has a class legacy widgets reference the widget by the class name and not by the id. We may add a special condition on legacy widgets block and reference existing class id widgets by their id if you think it is worth it, it would allow us to update existing widgets via legacy widget block and still save the widget in the same entry as before.
We also need some flags e.g: to specify if a widget is a callback widget or not.

'instance' => $this->get_sidebars_widget_instance( $sidebar, $item ),
),
'innerHTML' => '',
);
}
}
}

return array_merge(
$sidebar,
array( 'content' => serialize_blocks( $blocks ) )
);
}

protected function update_sidebar_data( $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 )
);
}

if ( isset( $request['content'] ) && is_string( $request['content'] ) ) {
$items = array();

$blocks = parse_blocks( $request['content'] );
foreach ( $blocks as $block ) {
if ( ! isset( $block['blockName'] ) ) {
continue;
}

if (
'core/legacy-widget' === $block['blockName'] &&
Copy link
Member

Choose a reason for hiding this comment

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

What if the user used the legacy widgets block, to use a widget that was never used before, and does not has any instance yet?

isset( $block['attrs']['identifier'] )
) {
$items[] = $block['attrs']['identifier'];

if ( isset( $block['attrs']['instance'] ) ) {
$this->update_widget_instance(
$block['attrs']['identifier'],
$block['attrs']['instance']
);
}
} else {
$items[] = $block;
}
}

gutenberg_set_sidebars_items( array_merge(
Copy link
Member

Choose a reason for hiding this comment

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

We are saving the blocks as PHP serialization of the parsed blocks result. Maybe we should save the blocks as an HTML string using our serialization format as we do in all other places.

gutenberg_get_sidebars_items(),
array( $sidebar_id => $items )
) );
}

return true;
}

private function get_sidebars_widget_instance( $sidebar, $id ) {
list( $object, $number, $name ) = $this->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;
}

private function update_widget_instance( $id, $new_instance ) {
list( $object, $number, ) = $this->get_widget_info( $id );
if ( ! $object ) {
return;
}

$object->_set( $number );

$instances = $object->get_settings();
$old_instance = $instances[ $number ];

$instance = $object->update( $new_instance, $old_instance );

/**
* Filters a widget's settings before saving.
*
* Returning false will effectively short-circuit the widget's ability
* to update settings.
*
* @since 2.8.0
*
* @param array $instance The current widget instance's settings.
* @param array $new_instance Array of new widget settings.
* @param array $old_instance Array of old widget settings.
* @param WP_Widget $this The current widget instance.
*/
$instance = apply_filters( 'widget_update_callback', $instance, $new_instance, $old_instance, $object );

if ( false !== $instance ) {
$instances[ $number ] = $instance;
$object->save_settings( $instances );
}
}

private function get_widget_info( $id ) {
global $wp_registered_widgets;

if (
! isset( $wp_registered_widgets[ $id ]['callback'][0] ) ||
! isset( $wp_registered_widgets[ $id ]['params'][0]['number'] ) ||
! isset( $wp_registered_widgets[ $id ]['name'] ) ||
! ( $wp_registered_widgets[ $id ]['callback'][0] instanceof WP_Widget )
) {
return array( null, null, null );
}

$object = $wp_registered_widgets[ $id ]['callback'][0];
$number = $wp_registered_widgets[ $id ]['params'][0]['number'];
$name = $wp_registered_widgets[ $id ]['name'];
return array( $object, $number, $name );
}
}
2 changes: 1 addition & 1 deletion lib/class-wp-rest-widget-updater-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class WP_REST_Widget_Updater_Controller extends WP_REST_Controller {
* @access public
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->namespace = '__experimental';
$this->rest_base = 'widgets';
}

Expand Down
4 changes: 4 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
if ( ! class_exists( 'WP_REST_Widget_Updater_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-widget-updater-controller.php';
}
if ( ! class_exists( 'WP_REST_Sidebars_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-sidebars-controller.php';
}
/**
* End: Include for phase 2
*/
Expand All @@ -26,6 +29,7 @@

require dirname( __FILE__ ) . '/blocks.php';
require dirname( __FILE__ ) . '/client-assets.php';
require dirname( __FILE__ ) . '/register.php';
require dirname( __FILE__ ) . '/demo.php';
require dirname( __FILE__ ) . '/widgets.php';
require dirname( __FILE__ ) . '/widgets-page.php';
Loading