Make WordPress Core

Changeset 57649


Ignore:
Timestamp:
02/17/2024 03:26:43 PM (3 years ago)
Author:
swissspidy
Message:

Interactivity API: Skip instead of bail out if HTML contains SVG or MATH.

Addresses an issue with server-side processing of directives when there is e.g. an SVG icon a navigation menu.

Props cbravobernal, westonruter, dmsnell, swissspidy.
Fixes #60517.

Location:
trunk
Files:
4 edited

Legend:

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

    r57563 r57649  
    182182
    183183        /**
     184         * Skips processing the content between tags.
     185         *
     186         * It positions the cursor in the closer tag of the foreign element, if it
     187         * exists.
     188         *
     189         * This function is intended to skip processing SVG and MathML inner content
     190         * instead of bailing out the whole processing.
     191         *
     192         * @since 6.5.0
     193         *
     194         * @access private
     195         *
     196         * @return bool Whether the foreign content was successfully skipped.
     197         */
     198        public function skip_to_tag_closer(): bool {
     199                $depth    = 1;
     200                $tag_name = $this->get_tag();
     201                while ( $depth > 0 && $this->next_tag(
     202                        array(
     203                                'tag_name'    => $tag_name,
     204                                'tag_closers' => 'visit',
     205                        )
     206                ) ) {
     207                        if ( $this->has_self_closing_flag() ) {
     208                                continue;
     209                        }
     210                        $depth += $this->is_tag_closer() ? -1 : 1;
     211                }
     212
     213                return 0 === $depth;
     214        }
     215
     216        /**
    184217         * Finds the matching closing tag for an opening tag.
    185218         *
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php

    r57646 r57649  
    236236                        $tag_name = $p->get_tag();
    237237
     238                        /*
     239                         * Directives inside SVG and MATH tags are not processed,
     240                         * as they are not compatible with the Tag Processor yet.
     241                         * We still process the rest of the HTML.
     242                         */
    238243                        if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) {
    239                                 $unbalanced = true;
    240                                 break;
     244                                $p->skip_to_tag_closer();
     245                                continue;
    241246                        }
    242247
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php

    r57563 r57649  
    1212 * @coversDefaultClass WP_Interactivity_API
    1313 */
    14 class Tests_WP_Interactivity_API extends WP_UnitTestCase {
     14class Tests_Interactivity_API_WpInteractivityAPI extends WP_UnitTestCase {
    1515        /**
    1616         * Instance of WP_Interactivity_API.
     
    509509
    510510        /**
    511          * Tests that the `process_directives` returns the same HTML if it finds an
    512          * SVG tag.
    513          *
    514          * @ticket 60356
    515          *
    516          * @covers ::process_directives
    517          */
    518         public function test_process_directives_doesnt_change_html_if_contains_svgs() {
    519                 $this->interactivity->state( 'myPlugin', array( 'id' => 'some-id' ) );
     511         * Tests that the `process_directives` process the HTML outside a SVG tag.
     512         *
     513         * @ticket 60517
     514         *
     515         * @covers ::process_directives
     516         */
     517        public function test_process_directives_changes_html_if_contains_svgs() {
     518                $this->interactivity->state(
     519                        'myPlugin',
     520                        array(
     521                                'id'    => 'some-id',
     522                                'width' => '100',
     523                        )
     524                );
    520525                $html           = '
    521                         <div data-wp-bind--id="myPlugin::state.id">
    522                                 <svg height="100" width="100">
     526                        <header>
     527                                <svg height="100" data-wp-bind--width="myPlugin::state.width">
     528                                        <title>Red Circle</title>
    523529                                        <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
    524                                 </svg>
    525                         </div>
     530                                </svg>
     531                                <div data-wp-bind--id="myPlugin::state.id"></div>
     532                                <div data-wp-bind--id="myPlugin::state.width"></div>
     533                        </header>
    526534                ';
    527535                $processed_html = $this->interactivity->process_directives( $html );
    528536                $p              = new WP_HTML_Tag_Processor( $processed_html );
    529                 $p->next_tag();
     537                $p->next_tag( 'svg' );
     538                $this->assertNull( $p->get_attribute( 'width' ) );
     539                $p->next_tag( 'div' );
     540                $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
     541                $p->next_tag( 'div' );
     542                $this->assertEquals( '100', $p->get_attribute( 'id' ) );
     543        }
     544
     545        /**
     546         * Tests that the `process_directives` does not process the HTML
     547         * inside SVG tags.
     548         *
     549         * @ticket 60517
     550         *
     551         * @covers ::process_directives
     552         */
     553        public function test_process_directives_does_not_change_inner_html_in_svgs() {
     554                $this->interactivity->state(
     555                        'myPlugin',
     556                        array(
     557                                'id' => 'some-id',
     558                        )
     559                );
     560                $html           = '
     561                        <header>
     562                                <svg height="100">
     563                                        <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
     564                                        <g data-wp-bind--id="myPlugin::state.id" />
     565                                </svg>
     566                        </header>
     567                ';
     568                $processed_html = $this->interactivity->process_directives( $html );
     569                $p              = new WP_HTML_Tag_Processor( $processed_html );
     570                $p->next_tag( 'div' );
    530571                $this->assertNull( $p->get_attribute( 'id' ) );
    531572        }
    532573
    533574        /**
    534          * Tests that the `process_directives` returns the same HTML if it finds an
     575         * Tests that the `process_directives` process the HTML outside the
    535576         * MathML tag.
    536577         *
    537          * @ticket 60356
    538          *
    539          * @covers ::process_directives
    540          */
    541         public function test_process_directives_doesnt_change_html_if_contains_math() {
    542                 $this->interactivity->state( 'myPlugin', array( 'id' => 'some-id' ) );
     578         * @ticket 60517
     579         *
     580         * @covers ::process_directives
     581         */
     582        public function test_process_directives_change_html_if_contains_math() {
     583                $this->interactivity->state(
     584                        'myPlugin',
     585                        array(
     586                                'id'   => 'some-id',
     587                                'math' => 'ml-id',
     588                        )
     589                );
    543590                $html           = '
    544                         <div data-wp-bind--id="myPlugin::state.id">
    545                                 <math>
     591                        <header>
     592                                <math data-wp-bind--id="myPlugin::state.math">
    546593                                        <mi>x</mi>
    547594                                        <mo>=</mo>
    548595                                        <mi>1</mi>
    549596                                </math>
    550                         </div>
     597                                <div data-wp-bind--id="myPlugin::state.id"></div>
     598                        </header>
    551599                ';
    552600                $processed_html = $this->interactivity->process_directives( $html );
    553601                $p              = new WP_HTML_Tag_Processor( $processed_html );
    554                 $p->next_tag();
     602                $p->next_tag( 'math' );
     603                $this->assertNull( $p->get_attribute( 'id' ) );
     604                $p->next_tag( 'div' );
     605                $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
     606        }
     607
     608        /**
     609         * Tests that the `process_directives` does not process the HTML
     610         * inside MathML tags.
     611         *
     612         * @ticket 60517
     613         *
     614         * @covers ::process_directives
     615         */
     616        public function test_process_directives_does_not_change_inner_html_in_math() {
     617                $this->interactivity->state(
     618                        'myPlugin',
     619                        array(
     620                                'id' => 'some-id',
     621                        )
     622                );
     623                $html           = '
     624                        <header>
     625                                <math data-wp-bind--id="myPlugin::state.math">
     626                                        <mrow data-wp-bind--id="myPlugin::state.id" />
     627                                        <mi>x</mi>
     628                                        <mo>=</mo>
     629                                        <mi>1</mi>
     630                                </math>
     631                        </header>
     632                ';
     633                $processed_html = $this->interactivity->process_directives( $html );
     634                $p              = new WP_HTML_Tag_Processor( $processed_html );
     635                $p->next_tag( 'div' );
    555636                $this->assertNull( $p->get_attribute( 'id' ) );
    556637        }
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php

    r57563 r57649  
    1212 * @coversDefaultClass WP_Interactivity_API_Directives_Processor
    1313 */
    14 class Tests_WP_Interactivity_API_Directives_Processor extends WP_UnitTestCase {
     14class Tests_Interactivity_API_WpInteractivityAPIDirectivesProcessor extends WP_UnitTestCase {
    1515        /**
    1616         * Tests the `get_content_between_balanced_template_tags` method on template
     
    779779                $this->assertFalse( $p->next_balanced_tag_closer_tag() );
    780780        }
     781
     782        /**
     783         * Tests that skip_to_tag_closer skips to the next tag,
     784         * independant of the content.
     785         *
     786         * @ticket 60517
     787         *
     788         * @covers ::skip_to_tag_closer
     789         */
     790        public function test_skip_to_tag_closer() {
     791                $content = '<div><span>Not closed</div>';
     792                $p       = new WP_Interactivity_API_Directives_Processor( $content );
     793                $p->next_tag();
     794                $this->assertTrue( $p->skip_to_tag_closer() );
     795                $this->assertTrue( $p->is_tag_closer() );
     796                $this->assertEquals( 'DIV', $p->get_tag() );
     797        }
     798
     799        /**
     800         * Tests that skip_to_tag_closer does not skip to the
     801         * next tag if there is no closing tag.
     802         *
     803         * @ticket 60517
     804         *
     805         * @covers ::skip_to_tag_closer
     806         */
     807        public function test_skip_to_tag_closer_bails_not_closed() {
     808                $content = '<div>Not closed parent';
     809                $p       = new WP_Interactivity_API_Directives_Processor( $content );
     810                $p->next_tag();
     811                $this->assertFalse( $p->skip_to_tag_closer() );
     812        }
     813
     814        /**
     815         * Tests that skip_to_tag_closer does not skip to the next
     816         * tag if the closing tag is different from the current tag.
     817         *
     818         * @ticket 60517
     819         *
     820         * @covers ::skip_to_tag_closer
     821         */
     822        public function test_skip_to_tag_closer_bails_different_tags() {
     823                $content = '<div></span>';
     824                $p       = new WP_Interactivity_API_Directives_Processor( $content );
     825                $p->next_tag();
     826                $this->assertFalse( $p->skip_to_tag_closer() );
     827        }
    781828}
Note: See TracChangeset for help on using the changeset viewer.