-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Server Side Render component and endpoint. (#5602)
Fixes #780.
- Loading branch information
Showing
10 changed files
with
694 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
ServerSideRender | ||
======= | ||
|
||
ServerSideRender is a component used for server-side rendering a preview of dynamic blocks to display in the editor. Server-side rendering in a block's `edit` function should be limited to blocks that are heavily dependent on existing PHP rendering logic that is heavily intertwined with data, particularly when there are no endpoints available. | ||
|
||
ServerSideRender may also be used when a legacy block is provided as a backwards compatibility measure, rather than needing to re-write the deprecated code that the block may depend on. | ||
|
||
ServerSideRender should be regarded as a fallback or legacy mechanism, it is not appropriate for developing new features against. | ||
|
||
New blocks should be built in conjunction with any necessary REST API endpoints, so that JavaScript can be used for rendering client-side in the `edit` function. This gives the best user experience, instead of relying on using the PHP `render_callback`. The logic necessary for rendering should be included in the endpoint, so that both the client-side JavaScript and server-side PHP logic should require a mininal amount of differences. | ||
|
||
## Usage | ||
|
||
Render core/archives preview. | ||
|
||
```jsx | ||
<ServerSideRender | ||
block="core/archives" | ||
attributes={ this.props.attributes } | ||
/> | ||
``` | ||
|
||
## Output | ||
|
||
Output uses the block's `render_callback` function, set when defining the block. | ||
|
||
## API Endpoint | ||
|
||
The API endpoint for getting the output for ServerSideRender is `/gutenberg/v1/block-renderer/:block`. It accepts any params, which are used as `attributes` for the block's `render_callback` method. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/** | ||
* External dependencies. | ||
*/ | ||
import { isEqual, isObject, map } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
Component, | ||
RawHTML, | ||
} from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
|
||
export class ServerSideRender extends Component { | ||
constructor( props ) { | ||
super( props ); | ||
this.state = { | ||
response: null, | ||
}; | ||
} | ||
|
||
componentDidMount() { | ||
this.fetch( this.props ); | ||
} | ||
|
||
componentWillReceiveProps( nextProps ) { | ||
if ( ! isEqual( nextProps, this.props ) ) { | ||
this.fetch( nextProps ); | ||
} | ||
} | ||
|
||
fetch( props ) { | ||
this.setState( { response: null } ); | ||
const { block, attributes } = props; | ||
|
||
const path = '/gutenberg/v1/block-renderer/' + block + '?context=edit&' + this.getQueryUrlFromObject( { attributes } ); | ||
|
||
return wp.apiRequest( { path: path } ).then( ( response ) => { | ||
if ( response && response.rendered ) { | ||
this.setState( { response: response.rendered } ); | ||
} | ||
} ); | ||
} | ||
|
||
getQueryUrlFromObject( obj, prefix ) { | ||
return map( obj, ( paramValue, paramName ) => { | ||
const key = prefix ? prefix + '[' + paramName + ']' : paramName, | ||
value = obj[ paramName ]; | ||
return isObject( paramValue ) ? this.getQueryUrlFromObject( value, key ) : | ||
encodeURIComponent( key ) + '=' + encodeURIComponent( value ); | ||
} ).join( '&' ); | ||
} | ||
|
||
render() { | ||
const response = this.state.response; | ||
if ( ! response || ! response.length ) { | ||
return ( | ||
<div key="loading" className="wp-block-embed is-loading"> | ||
|
||
<p>{ __( 'Loading...' ) }</p> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<RawHTML key="html">{ response }</RawHTML> | ||
); | ||
} | ||
} | ||
|
||
export default ServerSideRender; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
<?php | ||
/** | ||
* Block Renderer REST API: WP_REST_Block_Renderer_Controller class | ||
* | ||
* @package gutenberg | ||
* @since 2.8.0 | ||
*/ | ||
|
||
/** | ||
* Controller which provides REST endpoint for rendering a block. | ||
* | ||
* @since 2.8.0 | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class WP_REST_Block_Renderer_Controller extends WP_REST_Controller { | ||
|
||
/** | ||
* Constructs the controller. | ||
* | ||
* @access public | ||
*/ | ||
public function __construct() { | ||
$this->namespace = 'gutenberg/v1'; | ||
$this->rest_base = 'block-renderer'; | ||
} | ||
|
||
/** | ||
* Registers the necessary REST API routes, one for each dynamic block. | ||
* | ||
* @access public | ||
*/ | ||
public function register_routes() { | ||
$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); | ||
foreach ( $block_types as $block_type ) { | ||
if ( ! $block_type->is_dynamic() ) { | ||
continue; | ||
} | ||
|
||
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<name>' . $block_type->name . ')', array( | ||
'args' => array( | ||
'name' => array( | ||
'description' => __( 'Unique registered name for the block.', 'gutenberg' ), | ||
'type' => 'string', | ||
), | ||
), | ||
array( | ||
'methods' => WP_REST_Server::READABLE, | ||
'callback' => array( $this, 'get_item' ), | ||
'permission_callback' => array( $this, 'get_item_permissions_check' ), | ||
'args' => array( | ||
'context' => $this->get_context_param( array( 'default' => 'view' ) ), | ||
'attributes' => array( | ||
/* translators: %s is the name of the block */ | ||
'description' => sprintf( __( 'Attributes for %s block', 'gutenberg' ), $block_type->name ), | ||
'type' => 'object', | ||
'additionalProperties' => false, | ||
'properties' => $block_type->attributes, | ||
), | ||
'post_id' => array( | ||
'description' => __( 'ID of the post context.', 'gutenberg' ), | ||
'type' => 'integer', | ||
), | ||
), | ||
), | ||
'schema' => array( $this, 'get_public_item_schema' ), | ||
) ); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if a given request has access to read blocks. | ||
* | ||
* @since 2.8.0 | ||
* @access public | ||
* | ||
* @param WP_REST_Request $request Request. | ||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise. | ||
*/ | ||
public function get_item_permissions_check( $request ) { | ||
global $post; | ||
|
||
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0; | ||
|
||
if ( 0 < $post_id ) { | ||
$post = get_post( $post_id ); | ||
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) { | ||
return new WP_Error( 'gutenberg_block_cannot_read', __( 'Sorry, you are not allowed to read Gutenberg blocks of this post', 'gutenberg' ), array( | ||
'status' => rest_authorization_required_code(), | ||
) ); | ||
} | ||
} else { | ||
if ( ! current_user_can( 'edit_posts' ) ) { | ||
return new WP_Error( 'gutenberg_block_cannot_read', __( 'Sorry, you are not allowed to read Gutenberg blocks as this user.', 'gutenberg' ), array( | ||
'status' => rest_authorization_required_code(), | ||
) ); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Returns block output from block's registered render_callback. | ||
* | ||
* @since 2.8.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 get_item( $request ) { | ||
global $post; | ||
|
||
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0; | ||
|
||
if ( 0 < $post_id ) { | ||
$post = get_post( $post_id ); | ||
|
||
// Set up postdata since this will be needed if post_id was set. | ||
setup_postdata( $post ); | ||
} | ||
$registry = WP_Block_Type_Registry::get_instance(); | ||
$block = $registry->get_registered( $request['name'] ); | ||
|
||
if ( null === $block ) { | ||
return new WP_Error( 'gutenberg_block_invalid', __( 'Invalid block.', 'gutenberg' ), array( | ||
'status' => 404, | ||
) ); | ||
} | ||
|
||
$data = array( | ||
'rendered' => $block->render( $request->get_param( 'attributes' ) ), | ||
); | ||
return rest_ensure_response( $data ); | ||
} | ||
|
||
/** | ||
* Retrieves block's output schema, conforming to JSON Schema. | ||
* | ||
* @since 2.8.0 | ||
* @access public | ||
* | ||
* @return array Item schema data. | ||
*/ | ||
public function get_item_schema() { | ||
return array( | ||
'$schema' => 'http://json-schema.org/schema#', | ||
'title' => 'rendered-block', | ||
'type' => 'object', | ||
'properties' => array( | ||
'rendered' => array( | ||
'description' => __( 'The rendered block.', 'gutenberg' ), | ||
'type' => 'string', | ||
'required' => true, | ||
'context' => array( 'edit' ), | ||
), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.