Make WordPress Core

Changeset 57832


Ignore:
Timestamp:
03/14/2024 01:32:56 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.

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

Location:
trunk
Files:
2 edited

Legend:

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

    r57824 r57832  
    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 );
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPIFunctions.php

    r57826 r57832  
    451451        $this->assertEquals( '1', $processor->get_attribute( 'src' ) );
    452452    }
     453
     454    /**
     455     * Tests that context from void tags is not propagated to next tags.
     456     *
     457     * @ticket 60768
     458     *
     459     * @covers wp_interactivity_process_directives_of_interactive_blocks
     460     */
     461    public function test_process_context_directive_in_void_tags() {
     462        register_block_type(
     463            'test/custom-directive-block',
     464            array(
     465                'render_callback' => function () {
     466                    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>';
     467                },
     468                'supports'        => array(
     469                    'interactivity' => true,
     470                ),
     471            )
     472        );
     473        $post_content      = '<!-- wp:test/custom-directive-block /-->';
     474        $processed_content = do_blocks( $post_content );
     475        $processor         = new WP_HTML_Tag_Processor( $processed_content );
     476        $processor->next_tag(
     477            array(
     478                'tag_name' => 'input',
     479                'id'       => 'first-input',
     480            )
     481        );
     482        $first_input_value = $processor->get_attribute( 'value' );
     483        $processor->next_tag(
     484            array(
     485                'tag_name' => 'input',
     486                'id'       => 'second-input',
     487            )
     488        );
     489        $second_input_value = $processor->get_attribute( 'value' );
     490        unregister_block_type( 'test/custom-directive-block' );
     491        $this->assertEquals( 'inner', $first_input_value );
     492        $this->assertEquals( 'outer', $second_input_value );
     493    }
     494
     495    /**
     496     * Tests that namespace from void tags is not propagated to next tags.
     497     *
     498     * @ticket 60768
     499     *
     500     * @covers wp_interactivity_process_directives_of_interactive_blocks
     501     */
     502    public function test_process_interactive_directive_in_void_tags() {
     503        wp_interactivity_state(
     504            'void',
     505            array(
     506                'text' => 'void',
     507            )
     508        );
     509        register_block_type(
     510            'test/custom-directive-block',
     511            array(
     512                'render_callback' => function () {
     513                    return '<div data-wp-interactive="parent"><img data-wp-interactive="void" /><input data-wp-bind--value="state.text" /></div>';
     514                },
     515                'supports'        => array(
     516                    'interactivity' => true,
     517                ),
     518            )
     519        );
     520        $post_content      = '<!-- wp:test/custom-directive-block /-->';
     521        $processed_content = do_blocks( $post_content );
     522        $processor         = new WP_HTML_Tag_Processor( $processed_content );
     523        $processor->next_tag( array( 'tag_name' => 'input' ) );
     524        $input_value = $processor->get_attribute( 'value' );
     525        unregister_block_type( 'test/custom-directive-block' );
     526        $this->assertNull( $input_value );
     527    }
    453528}
Note: See TracChangeset for help on using the changeset viewer.