diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index 2190e125d08aa..506abd01537ab 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -183,8 +183,9 @@ final class WP_Interactivity_API { /** * Marker that makes the placeholders of the current `process_directives()` - * call recognizable. It occurs nowhere in the HTML that call received, and - * it differs from the marker of any call that the current one runs inside. + * call recognizable, or an empty string until the first `data-wp-html` host + * of the call renders and draws it. It holds a random UUID, so no text + * written before the call can realistically hold it. * * @since 7.2.0 * @var string @@ -536,17 +537,11 @@ public function process_directives( string $html ): string { /* * The trusted HTML of `data-wp-html` hosts never takes part in the * processing: each host receives a placeholder comment, and the comments - * are replaced by their HTML once the result is known. The marker that - * identifies them is chosen so that it occurs nowhere in the input, which - * makes it impossible for the input to contain a placeholder. It is also - * longer than the marker of any call this one runs inside, so their - * placeholders never coincide. + * are replaced by their HTML once the result is known. The call starts + * without the marker that identifies them: the first host that renders + * draws it, so a call without such a host does no marker work. */ - $marker = '' === $this->html_placeholder_marker ? 'wp-interactivity-dangerous-html' : $this->html_placeholder_marker . '_'; - while ( str_contains( $html, $marker ) ) { - $marker .= '_'; - } - $this->html_placeholder_marker = $marker; + $this->html_placeholder_marker = ''; $this->html_placeholders = array(); try { @@ -1698,8 +1693,13 @@ private function data_wp_html_processor( WP_Interactivity_API_Directives_Process /* * The HTML is held back and a placeholder comment takes its place, so no * later step of the call scans it. A comment is a single token for the - * processors, and the marker in it occurs nowhere in the input. + * processors. Its marker holds a random UUID, drawn once per call by the + * first host that renders, so no text written before the call can + * realistically contain a placeholder. */ + if ( '' === $this->html_placeholder_marker ) { + $this->html_placeholder_marker = 'wp-interactivity-dangerous-html-' . wp_generate_uuid4(); + } $placeholder = ''; if ( $p->skip_to_balanced_tag_closer( $placeholder ) ) { $this->html_placeholders[ $placeholder ] = $html; diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-html.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-html.php index dee43176fa721..5223808a71ebf 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-html.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI-wp-html.php @@ -1093,15 +1093,17 @@ public function test_host_inside_a_rendering_host_is_replaced_with_it() { } /** - * Tests that markup in the input that looks like a placeholder is not - * mistaken for one, however many marker variants it holds. + * Tests that markup in the input that looks like a placeholder, but holds a + * marker other than the call's own, is not mistaken for one. That includes + * the form of the call's own placeholders with a UUID other than the call's. * * @ticket 66246 * * @covers ::process_directives */ public function test_substitution_cannot_be_forged() { - $lookalikes = ''; + $lookalikes = '' . + ''; $this->set_state( array( 'html' => $this->token() ) ); $output = wp_interactivity_process_directives( @@ -1114,33 +1116,6 @@ public function test_substitution_cannot_be_forged() { ); } - /** - * Tests that HTML that looks like the placeholder of another host is - * inserted as given, not replaced by that host's HTML. - * - * @ticket 66246 - * - * @covers ::process_directives - */ - public function test_inserted_html_is_not_searched_for_placeholders() { - $lookalike = ''; - $this->set_state( - array( - 'first' => wp_interactivity_as_dangerous_html( $lookalike ), - 'second' => wp_interactivity_as_dangerous_html( 'second' ), - ) - ); - - $output = wp_interactivity_process_directives( - '

One

Two

' - ); - - $this->assertSame( - '

' . $lookalike . '

second

', - $output - ); - } - /** * Tests that unbalanced markup returns the input unchanged, with no HTML * inserted.