Make WordPress Core

Changeset 57832


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