Make WordPress Core

#66644 new enhancement

WP_REST_Blocks_Controller renders pattern content that it then removes from the response

Reported by: sanchothefat Owned by:
Priority: normal Milestone: Awaiting Review
Component: REST API Version:
Severity: normal Keywords:
Cc: Focuses:

Description

WP_REST_Blocks_Controller removes title.rendered and content.rendered from its schema and from every response, because a pattern has no rendered content on its own - only when it's within a post, page, or template on output. The rendering still happens, though and can be slow depending what hooks into the_content filter.

get_fields_for_response() only lists top-level fields, so it returns content. rest_is_field_included( 'content.rendered', $fields ) then returns true because its parent is included. So WP_REST_Posts_Controller::prepare_item_for_response() runs the_content on every pattern, and filter_response_by_context() throws the result away. The same happens with title.rendered and the_title.

The block editor requests /wp/v2/blocks?context=edit&per_page=100 without _fields each time it loads. That runs every block render callback and content filter for up to 100 patterns per page. On a site with image-heavy patterns, this was most of the request time.

Steps to reproduce

  1. Create a few synced patterns that contain blocks.
  2. Hook a counter onto the_content.
  3. Request /wp/v2/blocks?context=edit.
  4. the_content runs once for each pattern, but the response has no content.rendered.

Suggested fix

Override get_fields_for_response() in WP_REST_Blocks_Controller. Replace title and content with the sub-fields left in the controller's schema: title.raw, content.raw, content.protected and content.block_version. The response doesn't change, because rest_is_field_included() still treats a parent field as included when one of its children is listed.

<?php
public function get_fields_for_response( $request ) {
        $fields = parent::get_fields_for_response( $request );
        $schema = $this->get_item_schema();

        foreach ( array( 'title', 'content' ) as $field ) {
                $index = array_search( $field, $fields, true );
                if ( false === $index || empty( $schema['properties'][ $field ]['properties'] ) ) {
                        continue;
                }

                unset( $fields[ $index ] );
                foreach ( array_keys( $schema['properties'][ $field ]['properties'] ) as $sub_field ) {
                        $fields[] = "{$field}.{$sub_field}";
                }
        }

        return array_values( $fields );
}

Props to @rayhatron for the initial find here.

Change History (0)

Note: See TracTickets for help on using tickets.