diff --git a/components/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php b/components/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php
index 5c0452af..79b35b67 100644
--- a/components/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php
+++ b/components/DataLiberation/BlockMarkup/class-blockmarkupurlprocessor.php
@@ -436,6 +436,26 @@ public function is_url_absolute() {
return WPURL::can_parse( $this->get_raw_url() );
}
+ /**
+ * Returns true if the currently matched URL is a fragment-only reference,
+ * such as `#section` or a bare `#`.
+ *
+ * A fragment-only reference points within the document that contains it,
+ * so it has no origin of its own to rewrite. URL parsers strip leading C0
+ * control and space characters before resolving a reference, so a value
+ * like ` #section` counts as one too.
+ *
+ * @return bool Whether the currently matched URL is a fragment-only reference.
+ */
+ public function is_fragment_only_reference() {
+ $raw_url = $this->get_raw_url();
+ if ( ! is_string( $raw_url ) ) {
+ return false;
+ }
+
+ return 0 === strpos( ltrim( $raw_url, "\x00..\x20" ), '#' );
+ }
+
public function get_inspected_attribute_name() {
if ( '#tag' !== $this->get_token_type() ) {
return false;
diff --git a/components/DataLiberation/Tests/BlockMarkupUrlProcessorTest.php b/components/DataLiberation/Tests/BlockMarkupUrlProcessorTest.php
index 22193d9f..b45a1482 100644
--- a/components/DataLiberation/Tests/BlockMarkupUrlProcessorTest.php
+++ b/components/DataLiberation/Tests/BlockMarkupUrlProcessorTest.php
@@ -467,4 +467,26 @@ public function test_css_urls_with_regular_attributes() {
$expected = '
';
$this->assertEquals( $expected, $p->get_updated_html() );
}
+
+ /**
+ *
+ * @dataProvider provider_test_is_fragment_only_reference
+ */
+ public function test_is_fragment_only_reference( $markup, $expected ) {
+ $p = new BlockMarkupUrlProcessor( $markup, 'https://example.com' );
+ $this->assertTrue( $p->next_url(), 'Failed to find the URL in the markup.' );
+ $this->assertSame( $expected, $p->is_fragment_only_reference() );
+ }
+
+ public static function provider_test_is_fragment_only_reference() {
+ return array(
+ 'Named fragment' => array( 'Jump', true ),
+ 'Bare hash' => array( 'Top', true ),
+ 'Leading space' => array( 'Jump', true ),
+ 'Leading tab and newline' => array( "Jump", true ),
+ 'Fragment in a block attribute' => array( '', true ),
+ 'Absolute URL with a fragment' => array( 'Deep link', false ),
+ 'Relative path with a fragment' => array( 'Page', false ),
+ );
+ }
}
diff --git a/components/DataLiberation/Tests/RewriteUrlsTest.php b/components/DataLiberation/Tests/RewriteUrlsTest.php
index 17aca6d5..d28f96a5 100644
--- a/components/DataLiberation/Tests/RewriteUrlsTest.php
+++ b/components/DataLiberation/Tests/RewriteUrlsTest.php
@@ -213,6 +213,36 @@ public static function provider_test_wp_rewrite_urls() {
'https://🚀-science.com/science',
'https://science.wordpress.org',
),
+ 'Fragment-only reference in an HTML attribute is left alone' => array(
+ 'Jump',
+ 'Jump',
+ 'https://legacy-blog.com',
+ 'https://modern-webstore.org',
+ ),
+ 'Bare hash in an HTML attribute is left alone' => array(
+ 'Placeholder',
+ 'Placeholder',
+ 'https://legacy-blog.com',
+ 'https://modern-webstore.org',
+ ),
+ 'Fragment-only reference with leading whitespace is left alone' => array(
+ 'Jump',
+ 'Jump',
+ 'https://legacy-blog.com',
+ 'https://modern-webstore.org',
+ ),
+ 'Fragment-only reference in a block attribute is left alone' => array(
+ '',
+ '',
+ 'https://legacy-blog.com',
+ 'https://modern-webstore.org',
+ ),
+ 'Absolute URL carrying a fragment is still rewritten and keeps its fragment' => array(
+ 'Deep link',
+ 'Deep link',
+ 'https://legacy-blog.com',
+ 'https://modern-webstore.org',
+ ),
);
}
diff --git a/components/DataLiberation/URL/functions.php b/components/DataLiberation/URL/functions.php
index fe931ce3..d3646ef1 100644
--- a/components/DataLiberation/URL/functions.php
+++ b/components/DataLiberation/URL/functions.php
@@ -70,7 +70,18 @@ function wp_rewrite_urls( $options ) {
while ( $p->next_url() ) {
$token_type = $p->get_token_type();
$raw_url = $p->get_raw_url();
- $cache_key = $mapping_cache_key . "\0" . $token_type . "\0" . $raw_url;
+
+ /*
+ * Leave fragment-only references alone. Resolving one against the base
+ * URL makes it look like a child of the site being imported from, and
+ * rewriting it then turns an in-page anchor into a link somewhere else
+ * entirely: `#section` becomes `/#section`, and a bare `#` becomes `/`.
+ */
+ if ( $p->is_fragment_only_reference() ) {
+ continue;
+ }
+
+ $cache_key = $mapping_cache_key . "\0" . $token_type . "\0" . $raw_url;
$cached = $rewrite_cache->get( $cache_key );
if ( null !== $cached ) {