Make WordPress Core

Changeset 57834


Ignore:
Timestamp:
03/14/2024 03:10:55 PM (3 months ago)
Author:
swissspidy
Message:

Interactivity API: Do not propagate context from void tags to its siblings.

Resolves an issue where context on a void tag element such as <img> was incorrectly passed to following elements.
Adds tests.

Reviewed by gziolo.
Merges [57832] to the to the 6.5 branch.

Props santosguillamot, luisherranz, cbravobernal, dmsnell, gziolo, swissspidy.
Fixes #60768.

Location:
branches/6.5
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/6.5

  • branches/6.5/src/wp-includes/interactivity-api/class-wp-interactivity-api.php

    r57825 r57834  
    295295            }
    296296            /*
    297                 * If the matching opener tag didn't have any directives, it can skip the
    298                 * processing.
    299                 */
     297             * If the matching opener tag didn't have any directives, it can skip the
     298             * processing.
     299             */
    300300            if ( 0 === count( $directives_prefixes ) ) {
    301301                continue;
    302302            }
    303303
    304             /*
    305              * Sorts the attributes by the order of the `directives_processor` array
    306              * and checks what directives are present in this element. The processing
    307              * order is reversed for tag closers.
    308              */
    309             $directives_prefixes = array_intersect(
    310                 $p->is_tag_closer()
    311                     ? $directive_processor_prefixes_reversed
    312                     : $directive_processor_prefixes,
    313                 $directives_prefixes
     304            // Directive processing might be different depending on if it is entering the tag or exiting it.
     305            $modes = array(
     306                'enter' => ! $p->is_tag_closer(),
     307                'exit'  => $p->is_tag_closer() || ! $p->has_and_visits_its_closer_tag(),
    314308            );
    315309
    316             // Executes the directive processors present in this element.
    317             foreach ( $directives_prefixes as $directive_prefix ) {
    318                 $func = is_array( self::$directive_processors[ $directive_prefix ] )
    319                     ? self::$directive_processors[ $directive_prefix ]
    320                     : array( $this, self::$directive_processors[ $directive_prefix ] );
    321                 call_user_func_array(
    322                     $func,
    323                     array( $p, &$context_stack, &$namespace_stack, &$tag_stack )
     310            foreach ( $modes as $mode => $should_run ) {
     311                if ( ! $should_run ) {
     312                    continue;
     313                }
     314
     315                /*
     316                 * Sorts the attributes by the order of the `directives_processor` array
     317                 * and checks what directives are present in this element.
     318                 */
     319                $existing_directives_prefixes = array_intersect(
     320                    'enter' === $mode ? $directive_processor_prefixes : $directive_processor_prefixes_reversed,
     321                    $directives_prefixes
    324322                );
     323                foreach ( $existing_directives_prefixes as $directive_prefix ) {
     324                    $func = is_array( self::$directive_processors[ $directive_prefix ] )
     325                        ? self::$directive_processors[ $directive_prefix ]
     326                        : array( $this, self::$directive_processors[ $directive_prefix ] );
     327
     328                    call_user_func_array(
     329                        $func,
     330                        array( $p, $mode, &$context_stack, &$namespace_stack, &$tag_stack )
     331                    );
     332                }
    325333            }
    326334        }
     
    475483     *
    476484     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     485     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    477486     * @param array                                     $context_stack   The reference to the context stack.
    478487     * @param array                                     $namespace_stack The reference to the store namespace stack.
    479488     */
    480     private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
    481         // In closing tags, it removes the last namespace from the stack.
    482         if ( $p->is_tag_closer() ) {
     489    private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
     490        // When exiting tags, it removes the last namespace from the stack.
     491        if ( 'exit' === $mode ) {
    483492            array_pop( $namespace_stack );
    484493            return;
     
    519528     *
    520529     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     530     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    521531     * @param array                                     $context_stack   The reference to the context stack.
    522532     * @param array                                     $namespace_stack The reference to the store namespace stack.
    523533     */
    524     private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
    525         // In closing tags, it removes the last context from the stack.
    526         if ( $p->is_tag_closer() ) {
     534    private function data_wp_context_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
     535        // When exiting tags, it removes the last context from the stack.
     536        if ( 'exit' === $mode ) {
    527537            array_pop( $context_stack );
    528538            return;
     
    565575     *
    566576     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     577     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    567578     * @param array                                     $context_stack   The reference to the context stack.
    568579     * @param array                                     $namespace_stack The reference to the store namespace stack.
    569580     */
    570     private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
    571         if ( ! $p->is_tag_closer() ) {
     581    private function data_wp_bind_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
     582        if ( 'enter' === $mode ) {
    572583            $all_bind_directives = $p->get_attribute_names_with_prefix( 'data-wp-bind--' );
    573584
     
    609620     *
    610621     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     622     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    611623     * @param array                                     $context_stack   The reference to the context stack.
    612624     * @param array                                     $namespace_stack The reference to the store namespace stack.
    613625     */
    614     private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
    615         if ( ! $p->is_tag_closer() ) {
     626    private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
     627        if ( 'enter' === $mode ) {
    616628            $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' );
    617629
     
    643655     *
    644656     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     657     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    645658     * @param array                                     $context_stack   The reference to the context stack.
    646659     * @param array                                     $namespace_stack The reference to the store namespace stack.
    647660     */
    648     private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
    649         if ( ! $p->is_tag_closer() ) {
     661    private function data_wp_style_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
     662        if ( 'enter' === $mode ) {
    650663            $all_style_attributes = $p->get_attribute_names_with_prefix( 'data-wp-style--' );
    651664
     
    735748     *
    736749     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     750     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    737751     * @param array                                     $context_stack   The reference to the context stack.
    738752     * @param array                                     $namespace_stack The reference to the store namespace stack.
    739753     */
    740     private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack ) {
    741         if ( ! $p->is_tag_closer() ) {
     754    private function data_wp_text_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack ) {
     755        if ( 'enter' === $mode ) {
    742756            $attribute_value = $p->get_attribute( 'data-wp-text' );
    743757            $result          = $this->evaluate( $attribute_value, end( $namespace_stack ), end( $context_stack ) );
     
    832846     * @since 6.5.0
    833847     *
    834      * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
    835      */
    836     private function data_wp_router_region_processor( WP_Interactivity_API_Directives_Processor $p ) {
    837         if ( ! $p->is_tag_closer() && ! $this->has_processed_router_region ) {
     848     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     849     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
     850     */
     851    private function data_wp_router_region_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
     852        if ( 'enter' === $mode && ! $this->has_processed_router_region ) {
    838853            $this->has_processed_router_region = true;
    839854
     
    871886     *
    872887     * @param WP_Interactivity_API_Directives_Processor $p               The directives processor instance.
     888     * @param string                                    $mode            Whether the processing is entering or exiting the tag.
    873889     * @param array                                     $context_stack   The reference to the context stack.
    874890     * @param array                                     $namespace_stack The reference to the store namespace stack.
    875891     * @param array                                     $tag_stack       The reference to the tag stack.
    876892     */
    877     private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, array &$context_stack, array &$namespace_stack, array &$tag_stack ) {
    878         if ( ! $p->is_tag_closer() && 'TEMPLATE' === $p->get_tag() ) {
     893    private function data_wp_each_processor( WP_Interactivity_API_Directives_Processor $p, string $mode, array &$context_stack, array &$namespace_stack, array &$tag_stack ) {
     894        if ( 'enter' === $mode && 'TEMPLATE' === $p->get_tag() ) {
    879895            $attribute_name   = $p->get_attribute_names_with_prefix( 'data-wp-each' )[0];
    880896            $extracted_suffix = $this->extract_prefix_and_suffix( $attribute_name );
  • branches/6.5/tests/phpunit/tests/interactivity-api/interactivity-api.php

    r57830 r57834  
    424424        $this->assertEquals( 'data-wp-context=\'{"amp":"T\u0026T"}\'', wp_interactivity_data_wp_context( array( 'amp' => 'T&T' ) ) );
    425425    }
     426
     427    /**
     428     * Tests that context from void tags is not propagated to next tags.
     429     *
     430     * @ticket 60768
     431     *
     432     * @covers wp_interactivity_process_directives_of_interactive_blocks
     433     */
     434    public function test_process_context_directive_in_void_tags() {
     435        register_block_type(
     436            'test/custom-directive-block',
     437            array(
     438                'render_callback' => function () {
     439                    return '<div data-wp-interactive="nameSpace" data-wp-context=\'{"text": "outer"}\'><input id="first-input" data-wp-context=\'{"text": "inner"}\' data-wp-bind--value="context.text" /><input id="second-input" data-wp-bind--value="context.text" /></div>';
     440                },
     441                'supports'        => array(
     442                    'interactivity' => true,
     443                ),
     444            )
     445        );
     446        $post_content      = '<!-- wp:test/custom-directive-block /-->';
     447        $processed_content = do_blocks( $post_content );
     448        $processor         = new WP_HTML_Tag_Processor( $processed_content );
     449        $processor->next_tag(
     450            array(
     451                'tag_name' => 'input',
     452                'id'       => 'first-input',
     453            )
     454        );
     455        $first_input_value = $processor->get_attribute( 'value' );
     456        $processor->next_tag(
     457            array(
     458                'tag_name' => 'input',
     459                'id'       => 'second-input',
     460            )
     461        );
     462        $second_input_value = $processor->get_attribute( 'value' );
     463        unregister_block_type( 'test/custom-directive-block' );
     464        $this->assertEquals( 'inner', $first_input_value );
     465        $this->assertEquals( 'outer', $second_input_value );
     466    }
     467
     468    /**
     469     * Tests that namespace from void tags is not propagated to next tags.
     470     *
     471     * @ticket 60768
     472     *
     473     * @covers wp_interactivity_process_directives_of_interactive_blocks
     474     */
     475    public function test_process_interactive_directive_in_void_tags() {
     476        wp_interactivity_state(
     477            'void',
     478            array(
     479                'text' => 'void',
     480            )
     481        );
     482        register_block_type(
     483            'test/custom-directive-block',
     484            array(
     485                'render_callback' => function () {
     486                    return '<div data-wp-interactive="parent"><img data-wp-interactive="void" /><input data-wp-bind--value="state.text" /></div>';
     487                },
     488                'supports'        => array(
     489                    'interactivity' => true,
     490                ),
     491            )
     492        );
     493        $post_content      = '<!-- wp:test/custom-directive-block /-->';
     494        $processed_content = do_blocks( $post_content );
     495        $processor         = new WP_HTML_Tag_Processor( $processed_content );
     496        $processor->next_tag( array( 'tag_name' => 'input' ) );
     497        $input_value = $processor->get_attribute( 'value' );
     498        unregister_block_type( 'test/custom-directive-block' );
     499        $this->assertNull( $input_value );
     500    }
    426501}
Note: See TracChangeset for help on using the changeset viewer.