Make WordPress Core

Changeset 62658


Ignore:
Timestamp:
07/07/2026 09:44:41 PM (2 months ago)
Author:
adamsilverstein
Message:

Editor: Strip inline note markers from rendered block output.

Inline (partial-text) notes anchor to a text selection within a block via an in-content <mark class="wp-note" data-id="N"> marker. The marker stays in the raw post_content (and the REST raw view, revisions, and exports) so the editor can re-attach the note on reload, but the public front end should not expose note metadata.

See related Gutenberg pull request: https://github.com/WordPress/gutenberg/pull/78218.

Props mukesh27, dmsnell, westonruter.
Fixes #65482.

Location:
trunk
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/comment.php

    r62489 r62658  
    42724272        );
    42734273}
     4274
     4275/**
     4276 * Strips inline note markers from rendered block output.
     4277 *
     4278 * Inline notes - notes anchored to a text selection within a block rather than
     4279 * the whole block - are anchored in raw block content with
     4280 * `<mark class="wp-note" data-id="N">...</mark>` so the marker survives edits,
     4281 * but the public HTML should not expose note metadata. This filter unwraps the
     4282 * marker entirely - dropping the `<mark>` open tag and its matching closer while
     4283 * keeping the marked text - so nothing leaks to the front end. The raw
     4284 * `post_content` (and the REST `raw` view, revisions, exports) keeps the marker
     4285 * so the editor can re-attach it on reload.
     4286 *
     4287 * Only note markers are unwrapped: {@see WP_HTML_Tag_Processor::has_class()}
     4288 * matches the `wp-note` class by exact token, so a `<mark>` a user or plugin
     4289 * added (e.g. a `core/text-color` highlight, or an unrelated `wp-note-foo`
     4290 * class) is never flagged and survives byte-for-byte with all of its attributes
     4291 * intact. A naive regex would be wrong here: a `\bwp-note\b` word boundary also
     4292 * matches `wp-note-foo`, which is why the class check goes through the HTML API
     4293 * instead.
     4294 *
     4295 * The HTML API has no public token-removal method yet, so an anonymous
     4296 * {@see WP_HTML_Tag_Processor} subclass unwraps each note `<mark>` and its
     4297 * matching closer directly on the parsed token stream. Walking tokens - rather
     4298 * than matching `<mark>` with a regex - means a `</mark>`-looking sequence inside
     4299 * a comment or attribute value can never be mistaken for a real tag, and a
     4300 * nesting stack keeps each note opener paired with its own closer so overlapping
     4301 * notes and any user highlight `<mark>` left intact still resolve correctly.
     4302 *
     4303 * The low-level {@see WP_HTML_Tag_Processor} is used deliberately, rather than
     4304 * the tree-building {@see WP_HTML_Processor}. Note markers live in user-editable
     4305 * content, so the markup is not guaranteed to be well formed. On certain
     4306 * ill-formed nesting the tree builder aborts, which would leave note markers -
     4307 * and their metadata - in the rendered output. Scanning tokens instead removes
     4308 * every `wp-note` marker it encounters and degrades gracefully: an unbalanced or
     4309 * stray tag is left exactly as it was rather than corrupting surrounding markup.
     4310 *
     4311 * @since 7.1.0
     4312 *
     4313 * @param string $block_content Rendered block HTML.
     4314 * @return string Block HTML with `wp-note` markers unwrapped.
     4315 */
     4316function wp_strip_inline_note_markers( $block_content ) {
     4317        if ( ! str_contains( $block_content, 'wp-note' ) ) {
     4318                return $block_content;
     4319        }
     4320
     4321        /*
     4322         * Anonymous subclass exposing token removal, which WP_HTML_Tag_Processor
     4323         * does not provide publicly yet. Removing the current token via its bookmark
     4324         * span unwraps the `<mark>` (opener or closer) while keeping the text it
     4325         * wraps.
     4326         */
     4327        $processor = new class( $block_content ) extends WP_HTML_Tag_Processor {
     4328                /**
     4329                 * Removes the current token, keeping any text it wraps.
     4330                 */
     4331                public function remove_token(): void {
     4332                        // Always called after next_tag() returned true, so the bookmark is set.
     4333                        $this->set_bookmark( 'here' );
     4334                        $span = $this->bookmarks['here'];
     4335
     4336                        $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' );
     4337                }
     4338        };
     4339
     4340        /*
     4341         * Walk every `<mark>`, tracking note nesting on a stack so each note opener
     4342         * pairs with its own closer, and unwrap only the note markers.
     4343         */
     4344        $mark_stack = array();
     4345        $query      = array(
     4346                'tag_name'    => 'MARK',
     4347                'tag_closers' => 'visit',
     4348        );
     4349        while ( $processor->next_tag( $query ) ) {
     4350                if ( $processor->is_tag_closer() ) {
     4351                        $is_note = array_pop( $mark_stack );
     4352                } else {
     4353                        $is_note      = $processor->has_class( 'wp-note' );
     4354                        $mark_stack[] = $is_note;
     4355                }
     4356
     4357                if ( true === $is_note ) {
     4358                        $processor->remove_token();
     4359                }
     4360        }
     4361
     4362        return $processor->get_updated_html();
     4363}
  • trunk/src/wp-includes/default-filters.php

    r62652 r62658  
    790790add_filter( 'render_block', 'wp_render_typography_support', 10, 2 );
    791791
     792// Inline note markers.
     793add_filter( 'render_block', 'wp_strip_inline_note_markers' );
     794
    792795// User preferences.
    793796add_action( 'init', 'wp_register_persisted_preferences_meta' );
Note: See TracChangeset for help on using the changeset viewer.