Skip to content

Leave fragment-only references alone when rewriting URLs - #320

Open
mauteri wants to merge 1 commit into
WordPress:trunkfrom
mauteri:fix/do-not-rewrite-fragment-only-urls
Open

Leave fragment-only references alone when rewriting URLs#320
mauteri wants to merge 1 commit into
WordPress:trunkfrom
mauteri:fix/do-not-rewrite-fragment-only-urls

Conversation

@mauteri

@mauteri mauteri commented Sep 12, 2026

Copy link
Copy Markdown

The problem

wp_rewrite_urls() resolves every URL against the base URL before consulting the mapping. A fragment-only reference like #section has no host and no path, so after resolution it is indistinguishable from an ordinary same-site link, is_child_url_of() matches it, and it gets rewritten:

in out
<a href="#section"> <a href="/#section">
<a href="#"> <a href="/">
<!-- wp:button {"url":"#login"} --> <!-- wp:button {"url":"\/#login"} -->

An in-page anchor becomes a link to the site root, and a bare # loses its fragment entirely. In the importer this also breaks block validation: any block whose saved markup carries such an href (accordions, tab and anchor navigation, # placeholders handled by JavaScript) no longer matches its save() output after an import.

The fix

Skip references whose raw value starts with # before the cache and the mapping are consulted. The guard reads get_raw_url() on purpose, since parsing is what destroys the distinction. It is scoped to references that are only a fragment. A URL carrying a fragment alongside a host or path still rewrites and keeps its fragment (https://legacy-blog.com/page#section becomes https://modern-webstore.org/page#section), and that case is in the tests as a control.

Tests

Four cases added to RewriteUrlsTest::provider_test_wp_rewrite_urls(). Without the guard the three fragment-only cases fail with exactly the rewrites above; the absolute-URL-with-fragment control passes with and without it.

Context

This is the upstream half of WordPress/wordpress-importer#263, where the same change was made against the vendored copy under src/php-toolkit/. Per @desrosj's review there, the toolkit should land it first and the importer then pick it up. It is a small continue at the top of the loop in wp_rewrite_urls(), so it should rebase over #307 either way.

Query-only references (?filter=all) have the same shape and currently become /?filter=all. Left alone here to keep this to the reported behavior.

🤖 Generated with Claude Code

A reference like `#section` points within the document that contains
it, so there is no origin to migrate. Resolving it against the base URL
made it look like a child of the source site, and rewriting it turned an
in-page anchor into `/#section` and a bare `#` into `/`. Blocks whose
saved markup carries such an href then fail validation after an import.

Mirrors WordPress/wordpress-importer#263.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant