Commit cb1f1943b2 for wordpress.org
commit cb1f1943b23d8beac36daaad22364ec72995eba6
Author: luisherranz <luisherranz@git.wordpress.org>
Date: Mon Nov 9 08:58:53 2026 +0000
Interactivity API: Align server-side data-wp-html processing with client behavior.
Empty the element when its reference does not resolve to a trusted HTML token, and avoid processing directives inside content controlled by data-wp-html. Match the client's reference parsing, reserved-name filtering, namespace resolution, and conflicting-directive rules.
Ignore unsupported suffix and unique-ID entries while allowing their children to be processed. Ignore data-wp-html on void and text-only elements without evaluating its reference or changing their content.
Replace element content in a forward-only pass to avoid exceeding the HTML API's seek limit. Save and restore processing state around nested process_directives() calls.
Follow-up to [64386] and [64501].
Props santosguillamot.
Fixes #66246.
Built from https://develop.svn.wordpress.org/trunk@65343
git-svn-id: http://core.svn.wordpress.org/trunk@64483 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php b/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php
index bac24fba19..f3aeae5d7d 100644
--- a/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php
+++ b/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php
@@ -71,30 +71,52 @@ final class WP_Interactivity_API_Directives_Processor extends WP_HTML_Tag_Proces
* @return bool Whether the content was successfully replaced.
*/
public function set_content_between_balanced_tags( string $new_content ): bool {
- return $this->set_raw_content_between_balanced_tags( esc_html( $new_content ) );
+ $positions = $this->get_after_opener_tag_and_before_closer_tag_positions( true );
+ if ( ! $positions ) {
+ return false;
+ }
+ list( $after_opener_tag, $before_closer_tag ) = $positions;
+
+ $this->lexical_updates[] = new WP_HTML_Text_Replacement(
+ $after_opener_tag,
+ $before_closer_tag - $after_opener_tag,
+ esc_html( $new_content )
+ );
+
+ return true;
}
/**
- * Sets the content between two balanced tags, writing it as given.
+ * Moves to the matching closer tag of the current opener tag and replaces
+ * the content between them.
*
- * Unlike {@see WP_Interactivity_API_Directives_Processor::set_content_between_balanced_tags()},
- * it does not escape the content. Like it, it replaces the content between
- * the current opener tag and its matching closer tag, and it rewinds to the
- * opener tag.
+ * The matching closer tag is found as in
+ * {@see WP_Interactivity_API_Directives_Processor::next_balanced_tag_closer_tag()}.
+ * The new content is written as given. Unlike
+ * {@see WP_Interactivity_API_Directives_Processor::set_content_between_balanced_tags()},
+ * it neither applies the pending updates nor seeks back to the opener tag,
+ * so it can be called any number of times on the same processor.
*
* @since 7.2.0
*
* @access private
*
- * @param string $new_content The markup to replace the content between the matching tags. It is written as is.
- * @return bool Whether the content was successfully replaced.
+ * @param string $new_content The markup to replace the content between the matching tags with.
+ * @return bool Whether the matching closer tag was found. When it is not found, the cursor is at the end
+ * of the HTML and nothing is replaced.
*/
- public function set_raw_content_between_balanced_tags( string $new_content ): bool {
- $positions = $this->get_after_opener_tag_and_before_closer_tag_positions( true );
- if ( ! $positions ) {
+ public function skip_to_balanced_tag_closer( string $new_content ): bool {
+ $bookmarks = $this->get_balanced_tag_bookmarks();
+ if ( ! $bookmarks ) {
return false;
}
- list( $after_opener_tag, $before_closer_tag ) = $positions;
+ list( $opener_tag, $closer_tag ) = $bookmarks;
+
+ $after_opener_tag = $this->bookmarks[ $opener_tag ]->start + $this->bookmarks[ $opener_tag ]->length;
+ $before_closer_tag = $this->bookmarks[ $closer_tag ]->start;
+
+ $this->release_bookmark( $opener_tag );
+ $this->release_bookmark( $closer_tag );
$this->lexical_updates[] = new WP_HTML_Text_Replacement(
$after_opener_tag,
diff --git a/wp-includes/interactivity-api/class-wp-interactivity-api.php b/wp-includes/interactivity-api/class-wp-interactivity-api.php
index 5cdcffd14d..2190e125d0 100644
--- a/wp-includes/interactivity-api/class-wp-interactivity-api.php
+++ b/wp-includes/interactivity-api/class-wp-interactivity-api.php
@@ -52,6 +52,15 @@ final class WP_Interactivity_API {
'data-wp-each' => 'data_wp_each_processor',
);
+ /**
+ * Matches a directive value that names its namespace, as in
+ * `myPlugin::state.foo`, capturing the namespace and the rest of the value.
+ *
+ * @since 7.2.0
+ * @var string
+ */
+ private const NAMESPACED_VALUE_REGEX = '/^([\w\/-]+)::(.+)$/D';
+
/**
* Holds the initial state of the different Interactivity API stores.
*
@@ -130,6 +139,27 @@ final class WP_Interactivity_API {
*/
private $namespace_stack = null;
+ /**
+ * Stack of the namespaces that `data-wp-interactive` directives name, in
+ * step with the namespace stack, as the client reads them.
+ *
+ * This is only available during directive processing, otherwise it is `null`.
+ *
+ * Unlike the namespace stack, the entry of an element that carries the
+ * attribute named exactly `data-wp-interactive` never falls back to an
+ * enclosing namespace. It is `null` when the attribute names none: it has no
+ * value or an empty one, its JSON object holds no string `namespace`, or the
+ * namespace is `__proto__`, `constructor` or `prototype`. An element with
+ * only suffixed or unique-ID entries is no island, so its entry repeats the
+ * enclosing one. Only `data-wp-html` reads it, as the client resolves every
+ * directive.
+ *
+ * @since 7.2.0
+ * @var array<string|null>|null
+ * @phpstan-var list<string|null>|null
+ */
+ private $island_namespace_stack = null;
+
/**
* Stack of contexts defined by `data-wp-context` directives, in
* the order they are processed.
@@ -153,7 +183,8 @@ 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.
+ * 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.
*
* @since 7.2.0
* @var string
@@ -476,6 +507,9 @@ final class WP_Interactivity_API {
* @since 6.5.0
* @since 7.2.0 The output includes the trusted HTML that `data-wp-html` renders. That HTML is
* added once the whole call has finished, so it is never processed.
+ * @since 7.2.0 It can be called while another call is processing, as from a derived-state
+ * closure that renders markup. That call processes its HTML on its own, and the
+ * processing of the other call continues as before it once it returns.
*
* @param string $html The HTML content to process.
* @return string The processed HTML content. It returns the original content when the HTML contains unbalanced tags.
@@ -485,30 +519,49 @@ final class WP_Interactivity_API {
return $html;
}
- $this->namespace_stack = array();
- $this->context_stack = array();
+ $caller_state = array(
+ $this->namespace_stack,
+ $this->island_namespace_stack,
+ $this->context_stack,
+ $this->current_element,
+ $this->html_placeholder_marker,
+ $this->html_placeholders,
+ );
+
+ $this->namespace_stack = array();
+ $this->island_namespace_stack = array();
+ $this->context_stack = array();
+ $this->current_element = null;
/*
* 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.
+ * 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.
*/
- $marker = 'wp-interactivity-dangerous-html';
+ $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_placeholders = array();
- $result = $this->_process_directives( $html );
-
- $this->namespace_stack = null;
- $this->context_stack = null;
-
- $placeholders = $this->html_placeholders;
- $this->html_placeholders = array();
+ try {
+ $result = $this->_process_directives( $html );
+ $placeholders = $this->html_placeholders;
+ } finally {
+ list(
+ $this->namespace_stack,
+ $this->island_namespace_stack,
+ $this->context_stack,
+ $this->current_element,
+ $this->html_placeholder_marker,
+ $this->html_placeholders
+ ) = $caller_state;
+ }
if ( null === $result ) {
return $html;
@@ -523,7 +576,7 @@ final class WP_Interactivity_API {
* and updates the markup accordingly.
*
* It uses the WP_Interactivity_API instance's context and namespace stacks,
- * which are shared between all calls.
+ * which every call made during one `process_directives()` call shares.
*
* This method returns null if the HTML contains unbalanced tags.
*
@@ -544,8 +597,9 @@ final class WP_Interactivity_API {
* Save the current size for each stack to restore them in case
* the processing finds unbalanced tags.
*/
- $namespace_stack_size = count( $this->namespace_stack );
- $context_stack_size = count( $this->context_stack );
+ $namespace_stack_size = count( $this->namespace_stack );
+ $island_namespace_stack_size = count( $this->island_namespace_stack );
+ $context_stack_size = count( $this->context_stack );
while ( $p->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $p->get_tag();
@@ -645,7 +699,15 @@ final class WP_Interactivity_API {
'attributes' => $element_attrs,
);
+ $is_content_consumed = false;
+
foreach ( $modes as $mode => $should_run ) {
+ // An element whose content was consumed is completed as at its closer tag.
+ if ( 'exit' === $mode && $is_content_consumed ) {
+ array_pop( $tag_stack );
+ $should_run = true;
+ }
+
if ( ! $should_run ) {
continue;
}
@@ -664,6 +726,16 @@ final class WP_Interactivity_API {
: array( $this, self::$directive_processors[ $directive_prefix ] );
call_user_func_array( $func, array( $p, $mode, &$tag_stack ) );
+
+ /*
+ * A processor that consumes the content of the element it enters
+ * leaves the cursor on the element's closer tag, where no other
+ * processor enters. `data-wp-each` completes its `template` itself.
+ */
+ if ( 'enter' === $mode && $p->is_tag_closer() ) {
+ $is_content_consumed = 'data-wp-each' !== $directive_prefix;
+ break;
+ }
}
}
@@ -674,6 +746,7 @@ final class WP_Interactivity_API {
if ( $unbalanced ) {
// Reset the namespace and context stacks to their previous values.
array_splice( $this->namespace_stack, $namespace_stack_size );
+ array_splice( $this->island_namespace_stack, $island_namespace_stack_size );
array_splice( $this->context_stack, $context_stack_size );
}
@@ -1058,14 +1131,16 @@ final class WP_Interactivity_API {
* stack so that it's available for the nested interactivity elements.
*
* @since 6.5.0
+ * @since 7.2.0 Also records the namespace that the directive itself names, for `data-wp-html`.
*
* @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
* @param string $mode Whether the processing is entering or exiting the tag.
*/
private function data_wp_interactive_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) {
- // When exiting tags, it removes the last namespace from the stack.
+ // When exiting tags, it removes the last namespace from the stacks.
if ( 'exit' === $mode ) {
array_pop( $this->namespace_stack );
+ array_pop( $this->island_namespace_stack );
return;
}
@@ -1081,6 +1156,7 @@ final class WP_Interactivity_API {
* contained a valid namespace.
*/
$new_namespace = null;
+ $decoded_json = null;
if ( is_string( $attribute_value ) && ! empty( $attribute_value ) ) {
$decoded_json = json_decode( $attribute_value, true );
if ( is_array( $decoded_json ) ) {
@@ -1092,6 +1168,25 @@ final class WP_Interactivity_API {
$this->namespace_stack[] = ( $new_namespace && 1 === preg_match( '/^([\w\-_\/]+)/', $new_namespace ) )
? $new_namespace
: end( $this->namespace_stack );
+
+ /*
+ * Only the attribute named exactly `data-wp-interactive` makes an island,
+ * so an element with only suffixed or unique-ID entries keeps the
+ * enclosing island's namespace. The client reads a JSON object only when
+ * the value starts with `{`, and keeps any other value as given.
+ */
+ if ( null === $attribute_value ) {
+ $this->island_namespace_stack[] = array_last( $this->island_namespace_stack );
+ return;
+ }
+ $island_namespace = is_string( $attribute_value ) ? $attribute_value : null;
+ if ( is_array( $decoded_json ) && 1 === preg_match( '/^\s*\{/', $attribute_value ) ) {
+ $island_namespace = is_string( $decoded_json['namespace'] ?? null ) ? $decoded_json['namespace'] : null;
+ }
+ if ( '' === $island_namespace || ( null !== $island_namespace && self::is_reserved_word( $island_namespace ) ) ) {
+ $island_namespace = null;
+ }
+ $this->island_namespace_stack[] = $island_namespace;
}
/**
@@ -1511,18 +1606,27 @@ final class WP_Interactivity_API {
/**
* Processes the `data-wp-html` directive.
*
- * It sets the content of the current HTML element to the HTML of the token
- * that its associated reference resolves to. The HTML is written as given and
- * the directives inside it are not processed. See
+ * Only an entry without a suffix or a unique ID controls the content: the
+ * HTML of the token its reference resolves to, written as given, or nothing
+ * for any other value. No directive inside that content is processed. See
* {@see wp_interactivity_as_dangerous_html()} for the risks of marking HTML
* as trusted.
*
- * The element is left as it would be without the directive, and a notice
- * reports the reason, when the reference does not resolve to a token, the
- * element cannot hold content, or the element also carries `data-wp-text` or,
- * for a `template`, `data-wp-each`. A reference that resolves to `null`
- * declines without a notice. Only the entry without a suffix or a unique ID
- * is processed.
+ * HTML that closes the element, or that the browser moves out of it, such
+ * as a `div` in a `p`, is still written as given. The browser places the part
+ * that escapes after the element, where the client hydrates it as ordinary
+ * markup although the server did not process its directives.
+ *
+ * A value other than a token or `null` raises a notice. A reference that
+ * resolves to nothing does not.
+ *
+ * The element is processed as if it had no `data-wp-html` directive when it
+ * also carries `data-wp-text`, `data-wp-each` or `data-wp-router-region`, which
+ * raises a notice, or when its entries all have a suffix or unique ID or are
+ * dropped by the client. The directive is also ignored, with a notice, on void
+ * elements and on `script`, `style`, `textarea`, `title`, `iframe`, `xmp`,
+ * `noembed` and `noframes` elements. Their content is preserved and the
+ * reference is not evaluated.
*
* @since 7.2.0
*
@@ -1534,27 +1638,40 @@ final class WP_Interactivity_API {
return;
}
- // Get the first valid `data-wp-html` entry without suffix or unique ID.
- $valid_entry = array_find(
- $this->get_directive_entries( $p, 'html' ),
- fn( $entry ) => null === $entry['suffix'] && null === $entry['unique_id'] && ! empty( $entry['value'] )
- );
+ $directives = $this->get_client_directives( $p );
+ if ( empty( $directives['html'] ) ) {
+ return;
+ }
- if ( null === $valid_entry ) {
+ foreach ( array( 'text', 'each', 'router-region' ) as $conflicting_prefix ) {
+ if ( ! empty( $directives[ $conflicting_prefix ] ) ) {
+ _doing_it_wrong(
+ __METHOD__,
+ sprintf(
+ /* translators: %s: The directive combined with data-wp-html, such as data-wp-text. */
+ __( 'The "data-wp-html" directive cannot be combined with the "%s" directive on the same element. The "data-wp-html" directive is ignored.' ),
+ esc_html( 'data-wp-' . $conflicting_prefix )
+ ),
+ '7.2.0'
+ );
+ return;
+ }
+ }
+
+ $host_entry = array_find(
+ $directives['html'],
+ fn( $entry ) => null === $entry['suffix'] && null === $entry['unique_id']
+ );
+ if ( null === $host_entry ) {
return;
}
- /*
- * The structural reasons decline without evaluating the reference, so
- * their notice does not depend on the value and no derived state runs
- * for a result that would be discarded.
- */
if ( ! $p->has_and_visits_its_closer_tag() ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %s: The HTML tag name of the element, in lowercase. */
- __( 'The "data-wp-html" directive cannot set the content of a <%s> element because it cannot hold content. The element is left as it is.' ),
+ __( 'The "data-wp-html" directive is not supported on <%s> elements. The directive is ignored.' ),
esc_html( strtolower( $p->get_tag() ) )
),
'7.2.0'
@@ -1562,43 +1679,19 @@ final class WP_Interactivity_API {
return;
}
- $combined_directive = null;
- if ( array_find( $this->get_directive_entries( $p, 'text' ), fn( $entry ) => null === $entry['suffix'] && null === $entry['unique_id'] ) ) {
- $combined_directive = 'data-wp-text';
- } elseif ( 'TEMPLATE' === $p->get_tag() && ! empty( $this->get_directive_entries( $p, 'each' ) ) ) {
- $combined_directive = 'data-wp-each';
- }
- if ( null !== $combined_directive ) {
+ $result = $this->resolve_html_reference( $host_entry['value'] );
+ $html = WP_Interactivity_Dangerous_HTML::get_html( $result );
+
+ if ( null === $html && null !== $result ) {
_doing_it_wrong(
__METHOD__,
- sprintf(
- /* translators: %s: The directive combined with data-wp-html, such as data-wp-text. */
- __( 'The "data-wp-html" directive cannot be combined with the "%s" directive on the same element. The "data-wp-html" directive is ignored.' ),
- esc_html( $combined_directive )
- ),
+ __( 'The "data-wp-html" directive requires a reference that resolves to the value returned by wp_interactivity_as_dangerous_html(). Other values, such as strings of HTML, are not rendered and the content of the element is removed.' ),
'7.2.0'
);
- return;
- }
-
- /*
- * An entry value that is not a string is a JSON literal written in the
- * markup, which no store property can be named by, so it is not evaluated.
- */
- $result = is_string( $valid_entry['value'] ) ? $this->evaluate( $valid_entry ) : $valid_entry['value'];
-
- // Unresolved references and `null` values keep the element as it is, as they do in the client.
- if ( null === $result ) {
- return;
}
- $html = WP_Interactivity_Dangerous_HTML::get_html( $result );
if ( null === $html ) {
- _doing_it_wrong(
- __METHOD__,
- __( 'The "data-wp-html" directive requires a reference that resolves to the value returned by wp_interactivity_as_dangerous_html(). Other values, such as strings of HTML, are not rendered and the element is left as it is.' ),
- '7.2.0'
- );
+ $p->skip_to_balanced_tag_closer( '' );
return;
}
@@ -1608,11 +1701,132 @@ final class WP_Interactivity_API {
* processors, and the marker in it occurs nowhere in the input.
*/
$placeholder = '<!--' . $this->html_placeholder_marker . ':' . count( $this->html_placeholders ) . '-->';
- if ( $p->set_raw_content_between_balanced_tags( $placeholder ) ) {
+ if ( $p->skip_to_balanced_tag_closer( $placeholder ) ) {
$this->html_placeholders[ $placeholder ] = $html;
}
}
+ /**
+ * Gets the directives on the current element that the client keeps.
+ *
+ * The client drops a directive when a reserved word (`__proto__`,
+ * `constructor` or `prototype`) is its prefix, any `--`-separated part of its
+ * suffix or its unique ID, ignoring case and leading and trailing dashes, or
+ * when its value names a reserved word as its namespace.
+ *
+ * @since 7.2.0
+ *
+ * @param WP_Interactivity_API_Directives_Processor $p The directives processor instance.
+ * @return array[] The directives by prefix, such as `html`, each in the order of the element's attributes,
+ * with their raw values.
+ * @phpstan-return array<non-empty-string, list<array{
+ * suffix: non-empty-string|null,
+ * unique_id: non-empty-string|null,
+ * value: string|true,
+ * }>>
+ */
+ private function get_client_directives( WP_Interactivity_API_Directives_Processor $p ): array {
+ $directives = array();
+ foreach ( $p->get_attribute_names_with_prefix( 'data-wp-' ) ?? array() as $attribute_name ) {
+ $parsed_directive = $this->parse_directive_name( $attribute_name );
+ if ( null === $parsed_directive ) {
+ continue;
+ }
+
+ $name_parts = array( $parsed_directive['prefix'] );
+ if ( null !== $parsed_directive['suffix'] ) {
+ array_push( $name_parts, ...explode( '--', $parsed_directive['suffix'] ) );
+ }
+ if ( null !== $parsed_directive['unique_id'] ) {
+ $name_parts[] = $parsed_directive['unique_id'];
+ }
+ foreach ( $name_parts as $name_part ) {
+ if ( self::is_reserved_word( strtolower( trim( $name_part, '-' ) ) ) ) {
+ continue 2;
+ }
+ }
+
+ $value = $p->get_attribute( $attribute_name );
+ if ( is_string( $value ) && 1 === preg_match( self::NAMESPACED_VALUE_REGEX, $value, $matches ) && self::is_reserved_word( $matches[1] ) ) {
+ continue;
+ }
+
+ $directives[ $parsed_directive['prefix'] ][] = array(
+ 'suffix' => $parsed_directive['suffix'],
+ 'unique_id' => $parsed_directive['unique_id'],
+ 'value' => $value,
+ );
+ }
+ return $directives;
+ }
+
+ /**
+ * Resolves the reference of a `data-wp-html` directive as the client does.
+ *
+ * The reference is the raw attribute value, never decoded as JSON: an
+ * optional `<namespace>::`, an optional `!` and a dot-separated path.
+ * Without `<namespace>::`, the namespace is the one that the nearest
+ * `data-wp-interactive` directive names, never one inherited from a further
+ * one. When it names none, the reference resolves to `null`. It also
+ * resolves to `null`, without being evaluated, when its path is empty or
+ * starts with a second `!`, which names no property, or when a segment of
+ * its path is a reserved word (`__proto__`, `constructor` or `prototype`).
+ * The `!` applies after that, so `!state.x.constructor` resolves to `true`.
+ *
+ * @since 7.2.0
+ *
+ * @param string|true $reference The attribute value. It is `true` when the attribute has no value.
+ * @return mixed The value the reference resolves to, or `null` if it resolves to nothing.
+ */
+ private function resolve_html_reference( $reference ) {
+ if ( ! is_string( $reference ) ) {
+ return null;
+ }
+
+ if ( 1 === preg_match( self::NAMESPACED_VALUE_REGEX, $reference, $matches ) ) {
+ list( , $namespace, $path ) = $matches;
+ } else {
+ $namespace = array_last( $this->island_namespace_stack ?? array() );
+ $path = $reference;
+ }
+
+ $should_negate_value = false;
+ if ( str_starts_with( $path, '!' ) ) {
+ $path = substr( $path, 1 );
+ $should_negate_value = '' !== $path;
+ }
+
+ if (
+ '' === $path ||
+ str_starts_with( $path, '!' ) ||
+ null !== array_find( explode( '.', $path ), fn( $segment ) => self::is_reserved_word( $segment ) )
+ ) {
+ $value = null;
+ } else {
+ $value = $this->evaluate(
+ array(
+ 'namespace' => $namespace,
+ 'value' => $path,
+ )
+ );
+ }
+
+ return $should_negate_value ? ! $value : $value;
+ }
+
+ /**
+ * Checks whether a name is one the client never resolves or keeps as a
+ * directive name part, because it would reach the prototype of an object.
+ *
+ * @since 7.2.0
+ *
+ * @param string $name The name.
+ * @return bool Whether the name is `__proto__`, `constructor` or `prototype`.
+ */
+ private static function is_reserved_word( string $name ): bool {
+ return '__proto__' === $name || 'constructor' === $name || 'prototype' === $name;
+ }
+
/**
* Returns the CSS styles for animating the top loading bar in the router.
*
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 3232398eba..c88fbec3ce 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-beta4-65342';
+$wp_version = '7.2-beta4-65343';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.