Skip to content
Open
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
62 changes: 62 additions & 0 deletions src/wp-includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -3678,3 +3678,65 @@ function wp_is_rest_endpoint() {
*/
return (bool) apply_filters( 'wp_is_rest_endpoint', $is_rest_endpoint );
}

/**
* Determines whether a request asks for `content.blocks`.
*
* Blocks hold the raw stored markup, so like `content.raw` they are only
* returned in the `edit` context. Parsing adds to every response, so they
* are also opt-in: the request has to name `content.blocks` in `_fields`.
*
* @since 7.3.0
* @access private
*
* @param WP_REST_Request $request Request object.
* @param string[] $fields Fields to include in the response.
* @return bool Whether to return `content.blocks`.
*/
function rest_is_content_blocks_requested( WP_REST_Request $request, array $fields ): bool {
$requested = $request['_fields'];

return 'edit' === $request['context'] &&
rest_is_field_included( 'content.blocks', $fields ) &&
( is_string( $requested ) || is_array( $requested ) ) &&
in_array( 'content.blocks', wp_parse_list( $requested ), true );
}

/**
* Converts parsed blocks to the shape returned in `content.blocks`.
*
* Keys match the public properties of `WP_Block`. Whitespace-only freeform
* blocks between top-level blocks are dropped.
*
* @since 7.3.0
* @access private
*
* @param array<mixed> $parsed_blocks Blocks as returned by `parse_blocks()`.
* @return list<array<string, mixed>> Block data.
*/
function rest_prepare_blocks_for_response( array $parsed_blocks ): array {
$blocks = array();

foreach ( $parsed_blocks as $parsed_block ) {
if ( ! is_array( $parsed_block ) ) {
continue;
}

$name = isset( $parsed_block['blockName'] ) && is_string( $parsed_block['blockName'] ) ? $parsed_block['blockName'] : null;
$inner_html = isset( $parsed_block['innerHTML'] ) && is_string( $parsed_block['innerHTML'] ) ? $parsed_block['innerHTML'] : '';

if ( null === $name && '' === trim( $inner_html ) ) {
continue;
}

$blocks[] = array(
'name' => $name,
'attributes' => (object) ( isset( $parsed_block['attrs'] ) && is_array( $parsed_block['attrs'] ) ? $parsed_block['attrs'] : array() ),
'inner_blocks' => rest_prepare_blocks_for_response( isset( $parsed_block['innerBlocks'] ) && is_array( $parsed_block['innerBlocks'] ) ? $parsed_block['innerBlocks'] : array() ),
'inner_html' => $inner_html,
'inner_content' => isset( $parsed_block['innerContent'] ) && is_array( $parsed_block['innerContent'] ) ? $parsed_block['innerContent'] : array(),
);
}

return $blocks;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2178,6 +2178,19 @@ public function prepare_item_for_response( $item, $request ) {
$response = insert_hooked_blocks_into_rest_response( $response, $post );
}

$data = $response->get_data();
if ( is_array( $data ) && isset( $data['content'] ) && is_array( $data['content'] ) && rest_is_content_blocks_requested( $request, $fields ) ) {
$content = $post->post_content;

// Apply the same Block Hooks as `content.raw`, so both describe the same tree.
if ( in_array( $this->post_type, $content_like_post_types, true ) ) {
$content = apply_block_hooks_to_content_from_post_object( $content, $post, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
}

$data['content']['blocks'] = rest_prepare_blocks_for_response( parse_blocks( $content ) );
$response->set_data( $data );
}

/**
* Filters the post data for a REST API response.
*
Expand Down Expand Up @@ -2668,6 +2681,39 @@ public function get_item_schema() {
'context' => array( 'edit' ),
'readonly' => true,
),
'blocks' => array(
'description' => __( 'Blocks parsed from the content. Only returned when requested with _fields.' ),
'type' => 'array',
'context' => array( 'edit' ),
'readonly' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'Block name, or null for freeform content.' ),
'type' => array( 'string', 'null' ),
),
'attributes' => array(
'description' => __( 'Attributes stored in the block delimiter.' ),
'type' => 'object',
),
'inner_blocks' => array(
'description' => __( 'Nested blocks, in the same shape.' ),
'type' => 'array',
'items' => array( 'type' => 'object' ),
),
'inner_html' => array(
'description' => __( 'Block HTML without its inner blocks.' ),
'type' => 'string',
),
'inner_content' => array(
'description' => __( 'HTML chunks, with null where each inner block goes.' ),
'type' => 'array',
'items' => array( 'type' => array( 'string', 'null' ) ),
),
),
),
),
'protected' => array(
'description' => __( 'Whether the content is protected with a password.' ),
'type' => 'boolean',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,9 @@ public function prepare_item_for_response( $item, $request ) {
/** This filter is documented in wp-includes/post-template.php */
$data['content']['rendered'] = apply_filters( 'the_content', $post->post_content );
}
if ( rest_is_content_blocks_requested( $request, $fields ) ) {
$data['content']['blocks'] = rest_prepare_blocks_for_response( parse_blocks( $post->post_content ) );
}

if ( rest_is_field_included( 'excerpt', $fields ) ) {
$data['excerpt'] = array();
Expand Down
34 changes: 34 additions & 0 deletions tests/phpunit/tests/rest-api/rest-autosaves-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -1074,4 +1074,38 @@ public static function data_head_request_with_specified_fields_returns_success_r
'get_items request' => array( '/wp/v2/posts/%d' ),
);
}

/**
* @ticket 53603
*/
public function test_get_item_returns_blocks_when_requested_in_edit_context(): void {
wp_set_current_user( self::$editor_id );
$autosave_id = wp_create_post_autosave(
array(
'post_ID' => self::$post_id,
'post_type' => 'post',
'post_title' => 'Autosave',
'post_excerpt' => '',
'post_content' => '<!-- wp:paragraph --><p>Autosaved</p><!-- /wp:paragraph -->',
)
);
$this->assertIsInt( $autosave_id );

$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . $autosave_id );
$request->set_query_params(
array(
'context' => 'edit',
'_fields' => 'content.blocks',
)
);
$data = rest_get_server()->response_to_data( rest_get_server()->dispatch( $request ), false );
$this->assertIsArray( $data );
$this->assertIsArray( $data['content'] );
$blocks = $data['content']['blocks'];

$this->assertIsArray( $blocks );
$this->assertCount( 1, $blocks );
$this->assertIsArray( $blocks[0] );
$this->assertSame( '<p>Autosaved</p>', $blocks[0]['inner_html'] );
}
}
Loading
Loading