Make WordPress Core

Changeset 59662


Ignore:
Timestamp:
01/17/2025 09:35:50 PM (6 months ago)
Author:
joemcgill
Message:

Editor: Improve consistency of render_block_context filter.

This ensures that when block context is filtered via render_block_context, the filtered value is provided as available context to inner blocks.

For backwards compatibility reasons, filtered context is added to inner block context regardless of whether that block has declared support via the uses_context property.

Props mukesh27, flixos90, gziolo, dlh, joemcgill, santosguillamot.
Fixes #62046.

Location:
trunk
Files:
2 edited

Legend:

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

    r59361 r59662  
    5757     * @access protected
    5858     */
    59     protected $available_context;
     59    protected $available_context = array();
    6060
    6161    /**
     
    141141        $this->available_context = $available_context;
    142142
     143        $this->refresh_context_dependents();
     144    }
     145
     146    /**
     147     * Updates the context for the current block and its inner blocks.
     148     *
     149     * The method updates the context of inner blocks, if any, by passing down
     150     * any context values the block provides (`provides_context`).
     151     *
     152     * If the block has inner blocks, the method recursively processes them by creating new instances of `WP_Block`
     153     * for each inner block and updating their context based on the block's `provides_context` property.
     154     *
     155     * @since 6.8.0
     156     */
     157    public function refresh_context_dependents() {
     158        /*
     159         * Merging the `$context` property here is not ideal, but for now needs to happen because of backward compatibility.
     160         * Ideally, the `$context` property itself would not be filterable directly and only the `$available_context` would be filterable.
     161         * However, this needs to be separately explored whether it's possible without breakage.
     162         */
     163        $this->available_context = array_merge( $this->available_context, $this->context );
     164
    143165        if ( ! empty( $this->block_type->uses_context ) ) {
    144166            foreach ( $this->block_type->uses_context as $context_name ) {
     
    149171        }
    150172
    151         if ( ! empty( $block['innerBlocks'] ) ) {
     173        $this->refresh_parsed_block_dependents();
     174    }
     175
     176    /**
     177     * Updates the parsed block content for the current block and its inner blocks.
     178     *
     179     * This method sets the `inner_html` and `inner_content` properties of the block based on the parsed
     180     * block content provided during initialization. It ensures that the block instance reflects the
     181     * most up-to-date content for both the inner HTML and any string fragments around inner blocks.
     182     *
     183     * If the block has inner blocks, this method initializes a new `WP_Block_List` for them, ensuring the
     184     * correct content and context are updated for each nested block.
     185     *
     186     * @since 6.8.0
     187     */
     188    public function refresh_parsed_block_dependents() {
     189        if ( ! empty( $this->parsed_block['innerBlocks'] ) ) {
    152190            $child_context = $this->available_context;
    153191
     
    160198            }
    161199
    162             $this->inner_blocks = new WP_Block_List( $block['innerBlocks'], $child_context, $registry );
    163         }
    164 
    165         if ( ! empty( $block['innerHTML'] ) ) {
    166             $this->inner_html = $block['innerHTML'];
    167         }
    168 
    169         if ( ! empty( $block['innerContent'] ) ) {
    170             $this->inner_content = $block['innerContent'];
     200            $this->inner_blocks = new WP_Block_List( $this->parsed_block['innerBlocks'], $child_context, $this->registry );
     201        }
     202
     203        if ( ! empty( $this->parsed_block['innerHTML'] ) ) {
     204            $this->inner_html = $this->parsed_block['innerHTML'];
     205        }
     206
     207        if ( ! empty( $this->parsed_block['innerContent'] ) ) {
     208            $this->inner_content = $this->parsed_block['innerContent'];
    171209        }
    172210    }
     
    507545                        $block_content .= $pre_render;
    508546                    } else {
    509                         $source_block = $inner_block->parsed_block;
     547                        $source_block        = $inner_block->parsed_block;
     548                        $inner_block_context = $inner_block->context;
    510549
    511550                        /** This filter is documented in wp-includes/blocks.php */
     
    514553                        /** This filter is documented in wp-includes/blocks.php */
    515554                        $inner_block->context = apply_filters( 'render_block_context', $inner_block->context, $inner_block->parsed_block, $parent_block );
     555
     556                        /*
     557                         * The `refresh_context_dependents()` method already calls `refresh_parsed_block_dependents()`.
     558                         * Therefore the second condition is irrelevant if the first one is satisfied.
     559                         */
     560                        if ( $inner_block->context !== $inner_block_context ) {
     561                            $inner_block->refresh_context_dependents();
     562                        } elseif ( $inner_block->parsed_block !== $source_block ) {
     563                            $inner_block->refresh_parsed_block_dependents();
     564                        }
    516565
    517566                        $block_content .= $inner_block->render();
  • trunk/tests/phpunit/tests/blocks/renderBlock.php

    r56761 r59662  
    193193        $this->assertSame( array( 'example' => 'ok' ), $provided_context[0] );
    194194    }
     195
     196    /**
     197     * Tests the behavior of the 'render_block_context' filter based on the location of the filtered block.
     198     *
     199     * @ticket 62046
     200     */
     201    public function test_render_block_context_inner_blocks() {
     202        $provided_context = array();
     203
     204        register_block_type(
     205            'tests/context-provider',
     206            array(
     207                'provides_context' => array( 'example' ),
     208            )
     209        );
     210
     211        register_block_type(
     212            'tests/context-consumer',
     213            array(
     214                'uses_context'    => array( 'example' ),
     215                'render_callback' => static function ( $attributes, $content, $block ) use ( &$provided_context ) {
     216                    $provided_context = $block->context;
     217
     218                    return '';
     219                },
     220            )
     221        );
     222
     223        // Filter the context provided by the test block.
     224        add_filter(
     225            'render_block_context',
     226            function ( $context, $parsed_block ) {
     227                if ( isset( $parsed_block['blockName'] ) && 'tests/context-provider' === $parsed_block['blockName'] ) {
     228                    $context['example'] = 'ok';
     229                }
     230
     231                return $context;
     232            },
     233            10,
     234            2
     235        );
     236
     237        // Test inner block context when the provider block is a top-level block.
     238        do_blocks(
     239            <<<HTML
     240<!-- wp:tests/context-provider -->
     241<!-- wp:tests/context-consumer /-->
     242<!-- /wp:tests/context-provider -->
     243HTML
     244        );
     245        $this->assertArrayHasKey( 'example', $provided_context, 'Test block is top-level block: Context should include "example"' );
     246        $this->assertSame( 'ok', $provided_context['example'], 'Test block is top-level block: "example" in context should be "ok"' );
     247
     248        // Test inner block context when the provider block is an inner block.
     249        do_blocks(
     250            <<<HTML
     251<!-- wp:group {"layout":{"type":"constrained"}} -->
     252<!-- wp:tests/context-provider -->
     253<!-- wp:tests/context-consumer /-->
     254<!-- /wp:tests/context-provider -->
     255<!-- /wp:group -->
     256HTML
     257        );
     258        $this->assertArrayHasKey( 'example', $provided_context, 'Test block is inner block: Block context should include "example"' );
     259        $this->assertSame( 'ok', $provided_context['example'], 'Test block is inner block: "example" in context should be "ok"' );
     260    }
     261
     262    /**
     263     * Tests that the 'render_block_context' filter arbitrary context.
     264     *
     265     * @ticket 62046
     266     */
     267    public function test_render_block_context_allowed_context() {
     268        $provided_context = array();
     269
     270        register_block_type(
     271            'tests/context-consumer',
     272            array(
     273                'uses_context'    => array( 'example' ),
     274                'render_callback' => static function ( $attributes, $content, $block ) use ( &$provided_context ) {
     275                    $provided_context = $block->context;
     276
     277                    return '';
     278                },
     279            )
     280        );
     281
     282        // Filter the context provided to the test block.
     283        add_filter(
     284            'render_block_context',
     285            function ( $context, $parsed_block ) {
     286                if ( isset( $parsed_block['blockName'] ) && 'tests/context-consumer' === $parsed_block['blockName'] ) {
     287                    $context['arbitrary'] = 'ok';
     288                }
     289
     290                return $context;
     291            },
     292            10,
     293            2
     294        );
     295
     296        do_blocks(
     297            <<<HTML
     298<!-- wp:tests/context-consumer /-->
     299HTML
     300        );
     301        $this->assertArrayNotHasKey( 'arbitrary', $provided_context, 'Test block is top-level block: Block context should not include "arbitrary"' );
     302
     303        do_blocks(
     304            <<<HTML
     305<!-- wp:group {"layout":{"type":"constrained"}} -->
     306<!-- wp:tests/context-consumer /-->
     307<!-- /wp:group -->
     308HTML
     309        );
     310
     311        /*
     312         * These assertions assert something that ideally should not be the case: Inner blocks should respect the
     313         * `uses_context` value just like top-level blocks do. However, due to logic in `WP_Block::render()`, the
     314         * `context` property value itself is filterable when it should rather only apply to the `available_context`
     315         * property.
     316         * However, changing this behavior now would be a backward compatibility break, hence the assertion here.
     317         * Potentially it can be reconsidered in the future, so that these two assertions could be replaced with an
     318         * `assertArrayNotHasKey( 'arbitrary', $provided_context )`.
     319         */
     320        $this->assertArrayHasKey( 'arbitrary', $provided_context, 'Test block is inner block: Block context should include "arbitrary"' );
     321        $this->assertSame( 'ok', $provided_context['arbitrary'], 'Test block is inner block: "arbitrary" in context should be "ok"' );
     322    }
    195323}
Note: See TracChangeset for help on using the changeset viewer.