Make WordPress Core

Changeset 49608


Ignore:
Timestamp:
11/16/2020 12:45:55 AM (4 years ago)
Author:
noisysocks
Message:

Editor: Move pre_render_block, render_block_data, render_block_context

Move the pre_render_block, render_block_data, and render_block_context
filters from render_block() to WP_Block. This ensures that they are
called for all blocks, including nested blocks, not just top-level
blocks.

Fixes #51612.
Props gaambo, gziolo, TimothyBlynJacobs.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r49312 r49608  
    663663
    664664    /**
    665      * Allows render_block() to be short-circuited, by returning a non-null value.
     665     * Allows render_block() or WP_Block::render() to be short-circuited, by
     666     * returning a non-null value.
    666667     *
    667668     * @since 5.1.0
     
    675676    }
    676677
    677     $source_block = $parsed_block;
    678 
    679     /**
    680      * Filters the block being rendered in render_block(), before it's processed.
    681      *
    682      * @since 5.1.0
    683      *
    684      * @param array $parsed_block The block being rendered.
    685      * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
    686      */
    687     $parsed_block = apply_filters( 'render_block_data', $parsed_block, $source_block );
    688 
    689678    $context = array();
    690679
     
    707696        }
    708697    }
    709 
    710     /**
    711      * Filters the default context provided to a rendered block.
    712      *
    713      * @since 5.5.0
    714      *
    715      * @param array $context      Default context.
    716      * @param array $parsed_block Block being rendered, filtered by `render_block_data`.
    717      */
    718     $context = apply_filters( 'render_block_context', $context, $parsed_block );
    719698
    720699    $block = new WP_Block( $parsed_block, $context );
  • trunk/src/wp-includes/class-wp-block.php

    r49540 r49608  
    1212 * @since 5.5.0
    1313 * @property array $attributes
     14 * @property array $context
     15 * @property WP_Block[] $inner_blocks;
     16 * @property string $inner_html;
     17 * @property array $inner_content;
    1418 */
    1519class WP_Block {
     
    2428
    2529    /**
    26      * Name of block.
    27      *
    28      * @example "core/paragraph"
    29      *
    30      * @since 5.5.0
    31      * @var string
    32      */
    33     public $name;
    34 
    35     /**
    36      * Block type associated with the instance.
    37      *
    38      * @since 5.5.0
    39      * @var WP_Block_Type
    40      */
    41     public $block_type;
    42 
    43     /**
    44      * Block context values.
    45      *
    46      * @since 5.5.0
    47      * @var array
    48      */
    49     public $context = array();
    50 
    51     /**
    5230     * All available context of the current hierarchy.
    5331     *
     
    5937
    6038    /**
    61      * List of inner blocks (of this same class)
    62      *
    63      * @since 5.5.0
    64      * @var WP_Block[]
    65      */
    66     public $inner_blocks = array();
    67 
    68     /**
    69      * Resultant HTML from inside block comment delimiters after removing inner
    70      * blocks.
    71      *
    72      * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..."
     39     * Name of block.
     40     *
     41     * @example "core/paragraph"
    7342     *
    7443     * @since 5.5.0
    7544     * @var string
    7645     */
    77     public $inner_html = '';
    78 
    79     /**
    80      * List of string fragments and null markers where inner blocks were found
    81      *
    82      * @example array(
    83      *   'inner_html'    => 'BeforeInnerAfter',
    84      *   'inner_blocks'  => array( block, block ),
    85      *   'inner_content' => array( 'Before', null, 'Inner', null, 'After' ),
    86      * )
    87      *
    88      * @since 5.5.0
     46    public $name;
     47
     48    /**
     49     * Block type associated with the instance.
     50     *
     51     * @since 5.5.0
     52     * @var WP_Block_Type
     53     */
     54    public $block_type;
     55
     56    /**
     57     * Map of block property names and their cached value.
     58     *
     59     * Some block properties are computed lazily using a getter function. The
     60     * result is then cached here for subsequent use.
     61     *
     62     * @since 5.6.0
    8963     * @var array
    9064     */
    91     public $inner_content = array();
    92 
    93     /**
    94      * Constructor.
    95      *
    96      * Populates object properties from the provided block instance argument.
     65    protected $cached_properties = array();
     66
     67    /**
     68     * Creates a block instance from a backing `$parsed_block` array and list of
     69     * `$available_context`. From these, the block's dynamic properties can be
     70     * derived.
    9771     *
    9872     * The given array of context values will not necessarily be available on
     
    10478     * @since 5.5.0
    10579     *
    106      * @param array                  $block             Array of parsed block properties.
     80     * @param array                  $parsed_block      Array of parsed block properties.
    10781     * @param array                  $available_context Optional array of ancestry context values.
    10882     * @param WP_Block_Type_Registry $registry          Optional block type registry.
    10983     */
    110     public function __construct( $block, $available_context = array(), $registry = null ) {
    111         $this->parsed_block = $block;
    112         $this->name         = $block['blockName'];
    113 
     84    public function __construct( $parsed_block, $available_context = array(), $registry = null ) {
    11485        if ( is_null( $registry ) ) {
    115             $registry = WP_Block_Type_Registry::get_instance();
    116         }
    117 
    118         $this->block_type = $registry->get_registered( $this->name );
    119 
     86            $this->registry = WP_Block_Type_Registry::get_instance();
     87        } else {
     88            $this->registry = $registry;
     89        }
     90
     91        $this->reset( $parsed_block, $available_context );
     92    }
     93
     94    /**
     95     * Changes the backing `$parsed_block` and `$available_context` used to
     96     * derive the block's dynamic properties.
     97     *
     98     * @since 5.6.0
     99
     100     * @param array $parsed_block      Array of parsed block properties.
     101     * @param array $available_context Optional array of ancestry context values.
     102     * @param array $cached_properties Optional cache of dynamic properties to use.
     103     */
     104    protected function reset(
     105        $parsed_block,
     106        $available_context = array(),
     107        $cached_properties = array()
     108    ) {
     109        $this->parsed_block      = $parsed_block;
    120110        $this->available_context = $available_context;
     111        $this->name              = $parsed_block['blockName'];
     112        $this->block_type        = $this->registry->get_registered( $this->name );
     113        $this->cached_properties = $cached_properties;
     114    }
     115
     116    /**
     117     * Getter used for the block's dynamic properties:
     118     *
     119     * - `$block->attributes`
     120     * - `$block->context`
     121     * - `$block->inner_blocks`
     122     * - `$block->inner_html`
     123     * - `$block->inner_content`
     124     *
     125     * Each dynamic property is obtained by calling the associated getter
     126     * function (e.g. `this->get_attributes()`). The result is then cached in
     127     * `$this->cached_attributes` for subsequent calls.
     128     *
     129     * @since 5.5.0
     130     *
     131     * @param string $name Property name.
     132     * @return array|null Prepared attributes, or null.
     133     */
     134    public function __get( $name ) {
     135        if ( method_exists( $this, "get_$name" ) ) {
     136            if ( ! isset( $this->cached_properties[ $name ] ) ) {
     137                $this->cached_properties[ $name ] = $this->{"get_$name"}();
     138            }
     139
     140            return $this->cached_properties[ $name ];
     141        }
     142
     143        return null;
     144    }
     145
     146    /**
     147     * Block attributes.
     148     *
     149     * Use `$block->attributes` to access this.
     150     *
     151     * @since 5.6.0
     152     * @return array
     153     */
     154    protected function get_attributes() {
     155        $attributes = isset( $this->parsed_block['attrs'] ) ?
     156            $this->parsed_block['attrs'] :
     157            array();
     158
     159        if ( ! is_null( $this->block_type ) ) {
     160            return $this->block_type->prepare_attributes_for_render( $attributes );
     161        }
     162
     163        return $attributes;
     164    }
     165
     166    /**
     167     * Block context values.
     168     *
     169     * Use `$block->context` to access this.
     170     *
     171     * @since 5.6.0
     172     * @return array
     173     */
     174    protected function get_context() {
     175        $context = array();
    121176
    122177        if ( ! empty( $this->block_type->uses_context ) ) {
    123178            foreach ( $this->block_type->uses_context as $context_name ) {
    124179                if ( array_key_exists( $context_name, $this->available_context ) ) {
    125                     $this->context[ $context_name ] = $this->available_context[ $context_name ];
     180                    $context[ $context_name ] = $this->available_context[ $context_name ];
    126181                }
    127182            }
    128183        }
    129184
    130         if ( ! empty( $block['innerBlocks'] ) ) {
     185        return $context;
     186    }
     187
     188    /**
     189     * List of inner blocks (of this same class).
     190     *
     191     * Use `$block->inner_blocks` to access this.
     192     *
     193     * @since 5.6.0
     194     * @return WP_Block[]
     195     */
     196    protected function get_inner_blocks() {
     197        if ( ! empty( $this->parsed_block['innerBlocks'] ) ) {
    131198            $child_context = $this->available_context;
    132199
     
    139206            }
    140207
    141             $this->inner_blocks = new WP_Block_List( $block['innerBlocks'], $child_context, $registry );
    142         }
    143 
    144         if ( ! empty( $block['innerHTML'] ) ) {
    145             $this->inner_html = $block['innerHTML'];
    146         }
    147 
    148         if ( ! empty( $block['innerContent'] ) ) {
    149             $this->inner_content = $block['innerContent'];
    150         }
    151     }
    152 
    153     /**
    154      * Returns a value from an inaccessible property.
    155      *
    156      * This is used to lazily initialize the `attributes` property of a block,
    157      * such that it is only prepared with default attributes at the time that
    158      * the property is accessed. For all other inaccessible properties, a `null`
    159      * value is returned.
    160      *
    161      * @since 5.5.0
    162      *
    163      * @param string $name Property name.
    164      * @return array|null Prepared attributes, or null.
    165      */
    166     public function __get( $name ) {
    167         if ( 'attributes' === $name ) {
    168             $this->attributes = isset( $this->parsed_block['attrs'] ) ?
    169                 $this->parsed_block['attrs'] :
    170                 array();
    171 
    172             if ( ! is_null( $this->block_type ) ) {
    173                 $this->attributes = $this->block_type->prepare_attributes_for_render( $this->attributes );
    174             }
    175 
    176             return $this->attributes;
    177         }
    178 
    179         return null;
     208            return new WP_Block_List(
     209                $this->parsed_block['innerBlocks'],
     210                $child_context,
     211                $this->registry
     212            );
     213        }
     214
     215        return array();
     216    }
     217
     218    /**
     219     * Resultant HTML from inside block comment delimiters after removing inner
     220     * blocks.
     221     *
     222     * Use `$block->inner_html` to access this.
     223     *
     224     * @example "...Just <!-- wp:test /--> testing..." -> "Just testing..."
     225     *
     226     * @since 5.6.0
     227     * @return string
     228     */
     229    protected function get_inner_html() {
     230        if ( ! empty( $this->parsed_block['innerHTML'] ) ) {
     231            return $this->parsed_block['innerHTML'];
     232        }
     233
     234        return '';
     235    }
     236
     237    /**
     238     * List of string fragments and null markers where inner blocks were found
     239     *
     240     * Use `$block->inner_content` to access this.
     241     *
     242     * @example array(
     243     *   'inner_html'    => 'BeforeInnerAfter',
     244     *   'inner_blocks'  => array( block, block ),
     245     *   'inner_content' => array( 'Before', null, 'Inner', null, 'After' ),
     246     * )
     247     *
     248     * @since 5.6.0
     249     * @return array
     250     */
     251    protected function get_inner_content() {
     252        if ( ! empty( $this->parsed_block['innerContent'] ) ) {
     253            return $this->parsed_block['innerContent'];
     254        }
     255
     256        return array();
    180257    }
    181258
     
    194271    public function render( $options = array() ) {
    195272        global $post;
     273
     274        /** This filter is documented in src/wp-includes/blocks.php. */
     275        $pre_render = apply_filters( 'pre_render_block', null, $this->parsed_block );
     276        if ( ! is_null( $pre_render ) ) {
     277            return $pre_render;
     278        }
     279
    196280        $options = wp_parse_args(
    197281            $options,
     
    201285        );
    202286
    203         $is_dynamic    = $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic();
     287        $initial_parsed_block      = $this->parsed_block;
     288        $initial_available_context = $this->available_context;
     289        $initial_cached_properties = $this->cached_properties;
     290
     291        /**
     292         * Filters a block which is to be rendered by render_block() or
     293         * WP_Block::render().
     294         *
     295         * @since 5.1.0
     296         *
     297         * @param array $parsed_block The block being rendered.
     298         * @param array $source_block An un-modified copy of $parsed_block, as it appeared in the source content.
     299         */
     300        $parsed_block = apply_filters(
     301            'render_block_data',
     302            $this->parsed_block,
     303            $initial_parsed_block
     304        );
     305
     306        /**
     307         * Filters the default context of a block which is to be rendered by
     308         * render_block() or WP_Block::render().
     309         *
     310         * @since 5.5.0
     311         *
     312         * @param array $available_context Default context.
     313         * @param array $parsed_block      Block being rendered, filtered by `render_block_data`.
     314         */
     315        $available_context = apply_filters(
     316            'render_block_context',
     317            $this->available_context,
     318            $this->parsed_block
     319        );
     320
     321        $this->reset( $parsed_block, $available_context );
     322
     323        $is_dynamic = $options['dynamic']
     324            && $this->name
     325            && null !== $this->block_type
     326            && $this->block_type->is_dynamic();
     327
    204328        $block_content = '';
    205329
     
    219343            WP_Block_Supports::$block_to_render = $this->parsed_block;
    220344
    221             $block_content = (string) call_user_func( $this->block_type->render_callback, $this->attributes, $block_content, $this );
     345            $block_content = (string) call_user_func(
     346                $this->block_type->render_callback,
     347                $this->attributes,
     348                $block_content,
     349                $this
     350            );
    222351
    223352            WP_Block_Supports::$block_to_render = $parent;
     
    242371         * @param array  $block         The full block, including name and attributes.
    243372         */
    244         return apply_filters( 'render_block', $block_content, $this->parsed_block );
     373        $block_content = apply_filters( 'render_block', $block_content, $this->parsed_block );
     374
     375        $this->reset(
     376            $initial_parsed_block,
     377            $initial_available_context,
     378            $initial_cached_properties
     379        );
     380
     381        return $block_content;
    245382    }
    246383
  • trunk/tests/phpunit/tests/blocks/block.php

    r48937 r49608  
    4646    }
    4747
     48    function filter_pre_render_block( $pre_render, $parsed_block ) {
     49        if ( 'core/skip' === $parsed_block['blockName'] ) {
     50            return 'Hello world!';
     51        }
     52        return null;
     53    }
     54
     55    function filter_render_block_data( $parsed_block, $source_block ) {
     56        $parsed_block['attrs']['tag'] = $parsed_block['attrs']['tag'] . '-filtered';
     57        return $parsed_block;
     58    }
     59
     60    function filter_render_block_context( $available_context, $parsed_block ) {
     61        $available_context['core/recordId'] += 1;
     62        return $available_context;
     63    }
     64
    4865    /**
    4966     * @ticket 49927
     
    395412    }
    396413
     414    /**
     415     * @ticket 51612
     416     */
     417    function test_applies_pre_render_block_filter() {
     418        $this->registry->register( 'core/skip', array() );
     419
     420        add_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 10, 2 );
     421
     422        $parsed_blocks = parse_blocks( '<!-- wp:skip /-->' );
     423        $parsed_block  = $parsed_blocks[0];
     424        $context       = array();
     425        $block         = new WP_Block( $parsed_block, $context, $this->registry );
     426
     427        $rendered_content = $block->render();
     428
     429        remove_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ) );
     430
     431        $this->assertSame( 'Hello world!', $rendered_content );
     432    }
     433
     434    /**
     435     * @ticket 51612
     436     */
     437    function test_applies_pre_render_block_filter_to_inner_blocks() {
     438        $this->registry->register( 'core/outer', array() );
     439        $this->registry->register( 'core/skip', array() );
     440
     441        add_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 10, 2 );
     442
     443        $parsed_blocks = parse_blocks( '<!-- wp:outer --><!-- wp:skip /--> How are you?<!-- /wp:outer -->' );
     444        $parsed_block  = $parsed_blocks[0];
     445        $context       = array();
     446        $block         = new WP_Block( $parsed_block, $context, $this->registry );
     447
     448        $rendered_content = $block->render();
     449
     450        remove_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ) );
     451
     452        $this->assertSame( 'Hello world! How are you?', $rendered_content );
     453    }
     454
     455    /**
     456     * @ticket 51612
     457     */
     458    function test_applies_render_block_data_filter() {
     459        $this->registry->register(
     460            'core/wrapper',
     461            array(
     462                'attributes'      => array(
     463                    'tag' => array(
     464                        'type' => 'string',
     465                    ),
     466                ),
     467                'render_callback' => function( $block_attributes, $content ) {
     468                    return sprintf(
     469                        '<%1$s>%2$s</%1$s>',
     470                        $block_attributes['tag'],
     471                        $content
     472                    );
     473                },
     474            )
     475        );
     476
     477        add_filter( 'render_block_data', array( $this, 'filter_render_block_data' ), 10, 2 );
     478
     479        $parsed_blocks = parse_blocks( '<!-- wp:wrapper {"tag":"outer"} --><!-- wp:wrapper {"tag":"inner"} -->Hello!<!-- /wp:wrapper --><!-- /wp:wrapper -->' );
     480        $parsed_block  = $parsed_blocks[0];
     481        $context       = array();
     482        $block         = new WP_Block( $parsed_block, $context, $this->registry );
     483
     484        $rendered_content = $block->render();
     485
     486        remove_filter( 'render_block_data', array( $this, 'filter_render_block_data' ) );
     487
     488        $this->assertSame( '<outer-filtered><inner-filtered>Hello!</inner-filtered></outer-filtered>', $rendered_content );
     489    }
     490
     491    /**
     492     * @ticket 51612
     493     */
     494    function test_applies_render_block_context_filter() {
     495        $this->registry->register(
     496            'core/provider',
     497            array(
     498                'attributes'       => array(
     499                    'recordId' => array(
     500                        'type' => 'number',
     501                    ),
     502                ),
     503                'uses_context'     => array( 'core/recordId' ),
     504                'provides_context' => array(
     505                    'core/recordId' => 'recordId',
     506                ),
     507            )
     508        );
     509        $this->registry->register(
     510            'core/consumer',
     511            array(
     512                'uses_context'    => array( 'core/recordId' ),
     513                'render_callback' => function( $block_attributes, $content, $block ) {
     514                    return sprintf( 'Record ID: %d ', $block->context['core/recordId'] );
     515                },
     516            )
     517        );
     518
     519        add_filter( 'render_block_context', array( $this, 'filter_render_block_context' ), 10, 2 );
     520
     521        $parsed_blocks = parse_blocks( '<!-- wp:consumer /--><!-- wp:provider {"recordId":20} --><!-- wp:consumer /--><!-- /wp:provider -->' );
     522        $context       = array( 'core/recordId' => 10 );
     523
     524        $rendered_content = '';
     525
     526        foreach ( $parsed_blocks as $parsed_block ) {
     527            $block = new WP_Block( $parsed_block, $context, $this->registry );
     528
     529            $rendered_content .= $block->render();
     530        }
     531
     532        remove_filter( 'render_block_context', array( $this, 'filter_render_block_context' ) );
     533
     534        $this->assertSame( 'Record ID: 11 Record ID: 21 ', $rendered_content );
     535    }
     536
    397537}
Note: See TracChangeset for help on using the changeset viewer.