diff --git a/phpunit/tests/rewrite-urls.php b/phpunit/tests/rewrite-urls.php new file mode 100644 index 00000000..7a7360be --- /dev/null +++ b/phpunit/tests/rewrite-urls.php @@ -0,0 +1,94 @@ + $block_markup, + 'url-mapping' => array( self::FROM => self::TO ), + 'base_url' => self::FROM, + ) + ); + } + + /** + * Fragment-only references point within the document that contains them, + * so there is no origin to migrate and nothing to rewrite. + * + * Rewriting them resolves the fragment against the base URL first, which + * turns an in-page anchor into a link to a different page: `#section` + * became `/#section` and a bare `#` became `/`, dropping the fragment + * altogether. Blocks whose saved markup contains such an href then fail + * validation after an import. + * + * @covers ::WordPress\DataLiberation\URL\wp_rewrite_urls + * + * @dataProvider data_fragment_only_urls + * + * @param string $block_markup Markup containing a fragment-only reference. + */ + public function test_does_not_rewrite_fragment_only_urls( $block_markup ) { + $this->assertSame( + $block_markup, + $this->rewrite( $block_markup ), + 'Fragment-only references must survive a rewrite untouched.' + ); + } + + /** + * Data provider for fragment-only references. + * + * @return array> + */ + public function data_fragment_only_urls() { + return array( + 'named fragment' => array( 'Jump' ), + 'bare hash' => array( 'Placeholder' ), + 'hyphenated fragment' => array( 'iCal' ), + 'fragment in a block attribute' => array( '' ), + ); + } + + /** + * The guard is limited to references that are *only* a fragment. A real + * URL keeps being rewritten, and keeps its fragment while doing so. + * + * @covers ::WordPress\DataLiberation\URL\wp_rewrite_urls + */ + public function test_still_rewrites_absolute_urls_carrying_a_fragment() { + $this->assertSame( + 'Deep link', + $this->rewrite( 'Deep link' ) + ); + } + + /** + * Ordinary rewriting is unaffected. + * + * @covers ::WordPress\DataLiberation\URL\wp_rewrite_urls + */ + public function test_still_rewrites_absolute_urls() { + $this->assertSame( + 'Page', + $this->rewrite( 'Page' ) + ); + } +} diff --git a/src/php-toolkit/DataLiberation/URL/functions.php b/src/php-toolkit/DataLiberation/URL/functions.php index 18d793a2..0be93dc4 100644 --- a/src/php-toolkit/DataLiberation/URL/functions.php +++ b/src/php-toolkit/DataLiberation/URL/functions.php @@ -53,6 +53,19 @@ function wp_rewrite_urls( $options ) { $p = new BlockMarkupUrlProcessor( $options['block_markup'], $options['base_url'] ); while ( $p->next_url() ) { + /* + * Leave fragment-only references alone. A URL like `#section` points + * within the document that contains it, so there is no origin to + * migrate. Resolving it 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 `/`. + */ + $raw_url = $p->get_raw_url(); + if ( is_string( $raw_url ) && 0 === strpos( $raw_url, '#' ) ) { + continue; + } + $parsed_url = $p->get_parsed_url(); foreach ( $url_mapping as $mapping ) { if ( is_child_url_of( $parsed_url, $mapping['from_url'] ) ) {