Make WordPress Core

Changeset 57641


Ignore:
Timestamp:
02/16/2024 12:53:16 PM (3 months ago)
Author:
gziolo
Message:

Editor: Merge uses_context defined by block bindings sources with block types

Adds logic that fixes the limitation for souces by allowing merging the uses_context defined by block bindings sources into supported block types. Each source defines the context it needs and it is added to the block types that are using the block bindings API.

Fixes #60525.
Props santosguillamot, gziolo, czapla, thekt12.

Location:
trunk
Files:
9 edited

Legend:

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

    r57562 r57641  
    7676 *     The array of arguments that are used to register a source.
    7777 *
    78  *     @type string   $label              The label of the source.
    79  *     @type callback $get_value_callback A callback executed when the source is processed during block rendering.
    80  *                                        The callback should have the following signature:
     78 *     @type string   $label                   The label of the source.
     79 *     @type callback $get_value_callback      A callback executed when the source is processed during block rendering.
     80 *                                             The callback should have the following signature:
    8181 *
    82  *                                        `function ($source_args, $block_instance,$attribute_name): mixed`
    83  *                                            - @param array    $source_args    Array containing source arguments
    84  *                                                                              used to look up the override value,
    85  *                                                                              i.e. {"key": "foo"}.
    86  *                                            - @param WP_Block $block_instance The block instance.
    87  *                                            - @param string   $attribute_name The name of an attribute .
    88  *                                        The callback has a mixed return type; it may return a string to override
    89  *                                        the block's original value, null, false to remove an attribute, etc.
     82 *                                             `function ($source_args, $block_instance,$attribute_name): mixed`
     83 *                                                 - @param array    $source_args    Array containing source arguments
     84 *                                                                                   used to look up the override value,
     85 *                                                                                   i.e. {"key": "foo"}.
     86 *                                                 - @param WP_Block $block_instance The block instance.
     87 *                                                 - @param string   $attribute_name The name of an attribute .
     88 *                                             The callback has a mixed return type; it may return a string to override
     89 *                                             the block's original value, null, false to remove an attribute, etc.
     90 *     @type array    $uses_context (optional) Array of values to add to block `uses_context` needed by the source.
    9091 * }
    9192 * @return WP_Block_Bindings_Source|false Source when the registration was successful, or `false` on failure.
  • trunk/src/wp-includes/block-bindings/pattern-overrides.php

    r57585 r57641  
    4040            'label'              => _x( 'Pattern Overrides', 'block bindings source' ),
    4141            'get_value_callback' => '_block_bindings_pattern_overrides_get_value',
     42            'uses_context'       => array( 'pattern/overrides' ),
    4243        )
    4344    );
  • trunk/src/wp-includes/block-bindings/post-meta.php

    r57526 r57641  
    1414 * @access private
    1515 *
    16  * @param array $source_args Array containing source arguments used to look up the override value.
    17  *                           Example: array( "key" => "foo" ).
     16 * @param array    $source_args    Array containing source arguments used to look up the override value.
     17 *                                 Example: array( "key" => "foo" ).
     18 * @param WP_Block $block_instance The block instance.
    1819 * @return mixed The value computed for the source.
    1920 */
    20 function _block_bindings_post_meta_get_value( array $source_args ) {
    21     if ( ! isset( $source_args['key'] ) ) {
     21function _block_bindings_post_meta_get_value( array $source_args, $block_instance ) {
     22    if ( empty( $source_args['key'] ) ) {
    2223        return null;
    2324    }
    2425
    25     // Use the postId attribute if available.
    26     if ( isset( $source_args['postId'] ) ) {
    27         $post_id = $source_args['postId'];
    28     } else {
    29         // $block_instance->context['postId'] is not available in the Image block.
    30         $post_id = get_the_ID();
     26    if ( empty( $block_instance->context['postId'] ) ) {
     27        return null;
    3128    }
     29    $post_id = $block_instance->context['postId'];
    3230
    3331    // If a post isn't public, we need to prevent unauthorized users from accessing the post meta.
     
    5250            'label'              => _x( 'Post Meta', 'block bindings source' ),
    5351            'get_value_callback' => '_block_bindings_post_meta_get_value',
     52            'uses_context'       => array( 'postId', 'postType' ),
    5453        )
    5554    );
  • trunk/src/wp-includes/class-wp-block-bindings-registry.php

    r57575 r57641  
    3434
    3535    /**
     36     * Supported source properties that can be passed to the registered source.
     37     *
     38     * @since 6.5.0
     39     * @var array
     40     */
     41    private $allowed_source_properties = array(
     42        'label',
     43        'get_value_callback',
     44        'uses_context',
     45    );
     46
     47    /**
     48     * Supported blocks that can use the block bindings API.
     49     *
     50     * @since 6.5.0
     51     * @var array
     52     */
     53    private $supported_blocks = array(
     54        'core/paragraph',
     55        'core/heading',
     56        'core/image',
     57        'core/button',
     58    );
     59
     60    /**
    3661     * Registers a new block bindings source.
    3762     *
     
    5479     *     The array of arguments that are used to register a source.
    5580     *
    56      *     @type string   $label              The label of the source.
    57      *     @type callback $get_value_callback A callback executed when the source is processed during block rendering.
    58      *                                        The callback should have the following signature:
    59      *
    60      *                                        `function ($source_args, $block_instance,$attribute_name): mixed`
    61      *                                            - @param array    $source_args    Array containing source arguments
    62      *                                                                              used to look up the override value,
    63      *                                                                              i.e. {"key": "foo"}.
    64      *                                            - @param WP_Block $block_instance The block instance.
    65      *                                            - @param string   $attribute_name The name of the target attribute.
    66      *                                        The callback has a mixed return type; it may return a string to override
    67      *                                        the block's original value, null, false to remove an attribute, etc.
     81     *     @type string   $label                   The label of the source.
     82     *     @type callback $get_value_callback      A callback executed when the source is processed during block rendering.
     83     *                                             The callback should have the following signature:
     84     *
     85     *                                             `function ($source_args, $block_instance,$attribute_name): mixed`
     86     *                                                 - @param array    $source_args    Array containing source arguments
     87     *                                                                                   used to look up the override value,
     88     *                                                                                   i.e. {"key": "foo"}.
     89     *                                                 - @param WP_Block $block_instance The block instance.
     90     *                                                 - @param string   $attribute_name The name of the target attribute.
     91     *                                             The callback has a mixed return type; it may return a string to override
     92     *                                             the block's original value, null, false to remove an attribute, etc.
     93     *     @type array    $uses_context (optional) Array of values to add to block `uses_context` needed by the source.
    6894     * }
    6995     * @return WP_Block_Bindings_Source|false Source when the registration was successful, or `false` on failure.
     
    108134        }
    109135
    110         /* Validate that the source properties contain the label */
     136        // Validates that the source properties contain the label.
    111137        if ( ! isset( $source_properties['label'] ) ) {
    112138            _doing_it_wrong(
     
    118144        }
    119145
    120         /* Validate that the source properties contain the get_value_callback */
     146        // Validates that the source properties contain the get_value_callback.
    121147        if ( ! isset( $source_properties['get_value_callback'] ) ) {
    122148            _doing_it_wrong(
     
    128154        }
    129155
    130         /* Validate that the get_value_callback is a valid callback */
     156        // Validates that the get_value_callback is a valid callback.
    131157        if ( ! is_callable( $source_properties['get_value_callback'] ) ) {
    132158            _doing_it_wrong(
    133159                __METHOD__,
    134160                __( 'The "get_value_callback" parameter must be a valid callback.' ),
     161                '6.5.0'
     162            );
     163            return false;
     164        }
     165
     166        // Validates that the uses_context parameter is an array.
     167        if ( isset( $source_properties['uses_context'] ) && ! is_array( $source_properties['uses_context'] ) ) {
     168            _doing_it_wrong(
     169                __METHOD__,
     170                __( 'The "uses_context" parameter must be an array.' ),
     171                '6.5.0'
     172            );
     173            return false;
     174        }
     175
     176        if ( ! empty( array_diff( array_keys( $source_properties ), $this->allowed_source_properties ) ) ) {
     177            _doing_it_wrong(
     178                __METHOD__,
     179                __( 'The $source_properties array contains invalid properties.' ),
    135180                '6.5.0'
    136181            );
     
    145190        $this->sources[ $source_name ] = $source;
    146191
     192        // Adds `uses_context` defined by block bindings sources.
     193        add_filter(
     194            'get_block_type_uses_context',
     195            function ( $uses_context, $block_type ) use ( $source ) {
     196                if ( ! in_array( $block_type->name, $this->supported_blocks, true ) || empty( $source->uses_context ) ) {
     197                    return $uses_context;
     198                }
     199                // Use array_values to reset the array keys.
     200                return array_values( array_unique( array_merge( $uses_context, $source->uses_context ) ) );
     201            },
     202            10,
     203            2
     204        );
     205
    147206        return $source;
    148207    }
  • trunk/src/wp-includes/class-wp-block-bindings-source.php

    r57562 r57641  
    4747
    4848    /**
     49     * The context added to the blocks needed by the source.
     50     *
     51     * @since 6.5.0
     52     * @var array|null
     53     */
     54    public $uses_context = null;
     55
     56    /**
    4957     * Constructor.
    5058     *
     
    5866     */
    5967    public function __construct( string $name, array $source_properties ) {
    60         $this->name               = $name;
    61         $this->label              = $source_properties['label'];
    62         $this->get_value_callback = $source_properties['get_value_callback'];
     68        $this->name = $name;
     69        foreach ( $source_properties as $property_name => $property_value ) {
     70            $this->$property_name = $property_value;
     71        }
    6372    }
    6473
  • trunk/src/wp-includes/class-wp-block-type.php

    r57565 r57641  
    181181     * @var string[]
    182182     */
    183     public $uses_context = array();
     183    private $uses_context = array();
    184184
    185185    /**
     
    367367        }
    368368
     369        if ( 'uses_context' === $name ) {
     370            return $this->get_uses_context();
     371        }
     372
    369373        if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
    370374            return;
     
    395399     */
    396400    public function __isset( $name ) {
    397         if ( 'variations' === $name ) {
     401        if ( in_array( $name, array( 'variations', 'uses_context' ), true ) ) {
    398402            return true;
    399403        }
     
    418422     */
    419423    public function __set( $name, $value ) {
    420         if ( 'variations' === $name ) {
    421             $this->variations = $value;
    422             return;
    423         }
    424 
    425424        if ( ! in_array( $name, $this->deprecated_properties, true ) ) {
    426425            $this->{$name} = $value;
     
    617616        return apply_filters( 'get_block_type_variations', $this->variations, $this );
    618617    }
     618
     619    /**
     620     * Get block uses context.
     621     *
     622     * @since 6.5.0
     623     *
     624     * @return array[]
     625     */
     626    public function get_uses_context() {
     627        /**
     628         * Filters the registered uses context for a block type.
     629         *
     630         * @since 6.5.0
     631         *
     632         * @param array         $uses_context Array of registered uses context for a block type.
     633         * @param WP_Block_Type $block_type   The full block type object.
     634         */
     635        return apply_filters( 'get_block_type_uses_context', $this->uses_context, $this );
     636    }
    619637}
  • trunk/src/wp-includes/class-wp-block.php

    r57576 r57641  
    232232     */
    233233    private function process_block_bindings() {
    234         $parsed_block = $this->parsed_block;
    235 
    236         $computed_attributes = array();
    237 
    238         // Allowed blocks that support block bindings.
    239         // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes?
    240         $allowed_blocks = array(
     234        $parsed_block               = $this->parsed_block;
     235        $computed_attributes        = array();
     236        $supported_block_attributes = array(
    241237            'core/paragraph' => array( 'content' ),
    242238            'core/heading'   => array( 'content' ),
     
    245241        );
    246242
    247         // If the block doesn't have the bindings property, isn't one of the allowed
     243        // If the block doesn't have the bindings property, isn't one of the supported
    248244        // block types, or the bindings property is not an array, return the block content.
    249245        if (
    250             ! isset( $allowed_blocks[ $this->name ] ) ||
     246            ! isset( $supported_block_attributes[ $this->name ] ) ||
    251247            empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
    252248            ! is_array( $parsed_block['attrs']['metadata']['bindings'] )
     
    256252
    257253        foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) {
    258             // If the attribute is not in the allowed list, process next attribute.
    259             if ( ! in_array( $attribute_name, $allowed_blocks[ $this->name ], true ) ) {
     254            // If the attribute is not in the supported list, process next attribute.
     255            if ( ! in_array( $attribute_name, $supported_block_attributes[ $this->name ], true ) ) {
    260256                continue;
    261257            }
  • trunk/tests/phpunit/tests/block-bindings/render.php

    r57574 r57641  
    116116
    117117    /**
     118     * Tests passing `uses_context` as argument to the source.
     119     *
     120     * @ticket 60525
     121     *
     122     * @covers ::register_block_bindings_source
     123     */
     124    public function test_passing_uses_context_to_source() {
     125        $get_value_callback = function ( $source_args, $block_instance, $attribute_name ) {
     126            $value = $block_instance->context['sourceContext'];
     127            return "Value: $value";
     128        };
     129
     130        register_block_bindings_source(
     131            self::SOURCE_NAME,
     132            array(
     133                'label'              => self::SOURCE_LABEL,
     134                'get_value_callback' => $get_value_callback,
     135                'uses_context'       => array( 'sourceContext' ),
     136            )
     137        );
     138
     139        $block_content = <<<HTML
     140<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"test/source", "args": {"key": "test"}}}}} -->
     141<p>This should not appear</p>
     142<!-- /wp:paragraph -->
     143HTML;
     144        $parsed_blocks = parse_blocks( $block_content );
     145        $block         = new WP_Block( $parsed_blocks[0], array( 'sourceContext' => 'source context value' ) );
     146        $result        = $block->render();
     147
     148        $this->assertSame(
     149            'Value: source context value',
     150            $block->attributes['content'],
     151            "The 'content' should be updated with the value of the source context."
     152        );
     153        $this->assertSame(
     154            '<p>Value: source context value</p>',
     155            trim( $result ),
     156            'The block content should be updated with the value of the source context.'
     157        );
     158    }
     159
     160    /**
    118161     * Tests if the block content is updated with the value returned by the source
    119162     * for the Image block in the placeholder state.
  • trunk/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php

    r57562 r57641  
    4040                return 'test-value';
    4141            },
     42            'uses_context'       => array( 'sourceContext' ),
    4243        );
    4344    }
     
    158159
    159160        self::$test_source_properties['get_value_callback'] = 'not-a-callback';
     161
     162        $result = $this->registry->register( self::$test_source_name, self::$test_source_properties );
     163        $this->assertFalse( $result );
     164    }
     165
     166    /**
     167     * Should reject block bindings registration if `uses_context` is not an array.
     168     *
     169     * @ticket 60525
     170     *
     171     * @covers WP_Block_Bindings_Registry::register
     172     *
     173     * @expectedIncorrectUsage WP_Block_Bindings_Registry::register
     174     */
     175    public function test_register_invalid_string_uses_context() {
     176
     177        self::$test_source_properties['uses_context'] = 'not-an-array';
    160178
    161179        $result = $this->registry->register( self::$test_source_name, self::$test_source_properties );
     
    180198            $result
    181199        );
     200        $this->assertSame( 'test/source', $result->name );
     201        $this->assertSame( 'Test source', $result->label );
     202        $this->assertSame(
     203            'test-value',
     204            $result->get_value( array(), null, '' )
     205        );
     206        $this->assertEquals( array( 'sourceContext' ), $result->uses_context );
    182207    }
    183208
     
    322347        $this->assertTrue( $result );
    323348    }
     349
     350    /**
     351     * Tests merging `uses_context` from multiple sources.
     352     *
     353     * @ticket 60525
     354     *
     355     * @covers ::register_block_bindings_source
     356     * @covers WP_Block_Type::get_uses_context
     357     */
     358    public function test_merging_uses_context_from_multiple_sources() {
     359        $get_value_callback = function () {
     360            return 'Anything';
     361        };
     362
     363        $block_registry        = WP_Block_Type_Registry::get_instance();
     364        $original_uses_context = $block_registry->get_registered( 'core/paragraph' )->uses_context;
     365
     366        register_block_bindings_source(
     367            'test/source-one',
     368            array(
     369                'label'              => 'Test Source One',
     370                'get_value_callback' => $get_value_callback,
     371                'uses_context'       => array( 'commonContext', 'sourceOneContext' ),
     372            )
     373        );
     374
     375        register_block_bindings_source(
     376            'test/source-two',
     377            array(
     378                'label'              => 'Test Source Two',
     379                'get_value_callback' => $get_value_callback,
     380                'uses_context'       => array( 'commonContext', 'sourceTwoContext' ),
     381            )
     382        );
     383
     384        $new_uses_context = $block_registry->get_registered( 'core/paragraph' )->uses_context;
     385        // Checks that the resulting `uses_context` contains the values from both sources.
     386        $this->assertContains( 'commonContext', $new_uses_context );
     387        $this->assertContains( 'sourceOneContext', $new_uses_context );
     388        $this->assertContains( 'sourceTwoContext', $new_uses_context );
     389        // Checks that the resulting `uses_context` added 3 unique items.
     390        $this->assertSame( count( $original_uses_context ) + 3, count( $new_uses_context ) );
     391        // Checks that the array isn't sparse to prevent issues in the editor.
     392        $this->assertSame( array_key_last( $new_uses_context ), count( $new_uses_context ) - 1 );
     393    }
    324394}
Note: See TracChangeset for help on using the changeset viewer.