Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,11 @@ public function set_url( $raw_url, $parsed_url ) {
if ( null === $this->raw_url ) {
return false;
}
if ( $raw_url === $this->raw_url ) {
// A relative URL may resolve against a new base without changing its source spelling.
$this->parsed_url = $parsed_url;
return true;
}
$this->raw_url = $raw_url;
$this->parsed_url = $parsed_url;
switch ( parent::get_token_type() ) {
Expand Down Expand Up @@ -384,14 +389,15 @@ public function set_url( $raw_url, $parsed_url ) {

/**
* Rewrites the components of the currently matched URL from ones
* provided in $from_url to ones specified in $to_url.
* provided in $base_url to ones specified in $to_url.
*
* It preserves the relative nature of the matched URL.
* Structured values retain unmatched decoded URL bytes and their relative
* nature. Text nodes retain their complete-value behavior.
*
* @TODO: Should this method live in this class? It's specific to the import process
* and the URL rewriting logic and has knowledge about the quirks of detecting
* relative URLs in text nodes. On the other hand, the detection is performed
* by this WPURL_In_Text_Processor class so maybe the two do go hand in hand?
* relative URLs in text nodes. On the other hand, URLInTextProcessor performs
* that detection, so maybe the two do go hand in hand?
*/
public function replace_base_url( $to_url, $base_url = null ) {
$base_url = $base_url ?? $this->base_url_object;
Expand Down Expand Up @@ -421,10 +427,14 @@ public function replace_base_url( $to_url, $base_url = null ) {
if ( false === $result ) {
return false;
}
if ( '#text' === parent::get_token_type() ) {
return $this->set_url( (string) $result, $result->new_url );
}
if ( null === $result->new_source_preserving_raw_url ) {
return false;
}

$this->set_url( $result . '', $result->new_url );

return true;
return $this->set_url( $result->new_source_preserving_raw_url, $result->new_url );
}

/**
Expand Down
37 changes: 25 additions & 12 deletions components/DataLiberation/CSS/class-cssprocessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -888,23 +888,36 @@ public function set_token_value( string $new_value ): bool {
// Only URL and string tokens are currently supported.
switch ( $this->token_type ) {
case self::TOKEN_URL:
$this->lexical_updates[] = array(
'start' => $this->token_value_starts_at,
'length' => $this->token_value_length,
'text' => $this->escape_url_value( $new_value ),
);
return true;
$update_start = $this->token_value_starts_at;
$update_length = $this->token_value_length;
break;
case self::TOKEN_STRING:
$this->lexical_updates[] = array(
'start' => $this->token_starts_at,
'length' => $this->token_length,
'text' => $this->escape_url_value( $new_value ),
);
return true;
$update_start = $this->token_starts_at;
$update_length = $this->token_length;
break;
default:
_doing_it_wrong( __METHOD__, 'set_token_value() only supports URL and string tokens. Got token type: ' . $this->token_type, '1.0.0' );
return false;
}

$escaped_value = $this->escape_url_value( $new_value );
$last_update_index = count( $this->lexical_updates ) - 1;
// A later write to the current token supersedes its pending update.
if (
0 <= $last_update_index &&
$update_start === $this->lexical_updates[ $last_update_index ]['start'] &&
$update_length === $this->lexical_updates[ $last_update_index ]['length']
) {
$this->lexical_updates[ $last_update_index ]['text'] = $escaped_value;
return true;
}

$this->lexical_updates[] = array(
'start' => $update_start,
'length' => $update_length,
'text' => $escaped_value,
);
return true;
}

/**
Expand Down
72 changes: 71 additions & 1 deletion components/DataLiberation/Tests/BlockMarkupUrlProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public static function provider_test_set_url_examples() {
'In the "url" block attribute of a navigation-link block' => array(
'<!-- wp:navigation-link {"url": "https://w.org"} /-->',
'https://w.org',
'<!-- wp:navigation-link {"url":"https:\/\/w.org"} /-->',
'<!-- wp:navigation-link {"url": "https://w.org"} /-->',
),
'In a text node' => array(
'Have you seen https://wordpress.org yet?',
Expand Down Expand Up @@ -305,6 +305,76 @@ public static function provider_test_next_url_replace_base_url() {
);
}

/**
* @dataProvider provider_replace_base_url_in_structured_values
*/
public function test_replace_base_url_in_structured_values( $markup, $expected ) {
$p = new BlockMarkupUrlProcessor( $markup, 'http://old.example/media' );

$this->assertTrue( $p->next_url() );
$this->assertTrue( $p->replace_base_url( 'https://new.example/assets' ) );
$this->assertSame( $expected, $p->get_updated_html() );
}

public static function provider_replace_base_url_in_structured_values() {
return array(
'HTML attribute' => array(
'<a href="http://old.example/media/a/./b/../%7e//file?raw=%2f+%20#F%72ag"></a>',
'<a href="https://new.example/assets/a/./b/../%7e//file?raw=%2f+%20#F%72ag"></a>',
),
'CSS URL' => array(
'<div style="background:url(http://old.example/media/file?raw=%2f+%20#F%72ag)"></div>',
'<div style="background:url(&quot;https://new.example/assets/file?raw=%2f+%20#F%72ag&quot;)"></div>',
),
'block attribute' => array(
'<!-- wp:image {"url":"http:\/\/old.example\/%6dedia\/file?raw=%2f+%20#F%72ag"} /-->',
'<!-- wp:image {"url":"https:\/\/new.example\/assets\/file?raw=%2f+%20#F%72ag"} /-->',
),
);
}

public function test_replace_base_url_updates_parsed_url_without_a_source_replacement() {
$markup = '<A HREF=\'//old.example/media/file\'></A>';
$p = new BlockMarkupUrlProcessor( $markup, 'http://old.example/media' );

$this->assertTrue( $p->next_url() );
$this->assertTrue( $p->replace_base_url( 'https://old.example/media' ) );
$this->assertSame( 'https://old.example/media/file', $p->get_parsed_url()->toString() );
$this->assertSame( $markup, $p->get_updated_html() );
}

public function test_replace_base_url_rewrites_later_css_urls_after_rendering_an_earlier_update() {
$markup = '<div style="background:url(http://very-long-old.example/very/long/base/one),url(http://very-long-old.example/very/long/base/two)"></div>';
$p = new BlockMarkupUrlProcessor( $markup, 'http://very-long-old.example/very/long/base' );

$this->assertTrue( $p->next_url() );
$this->assertTrue( $p->replace_base_url( 'https://n.example/x' ) );
$p->get_updated_html();

$this->assertTrue( $p->next_url() );
$this->assertTrue( $p->replace_base_url( 'https://n.example/x' ) );
$this->assertSame(
'<div style="background:url(&quot;https://n.example/x/one&quot;),url(&quot;https://n.example/x/two&quot;)"></div>',
$p->get_updated_html()
);
}

public function test_set_url_replaces_a_pending_css_base_update() {
$p = new BlockMarkupUrlProcessor(
'<div style="background:url(http://old.example/media/first),url(http://old.example/media/second)"></div>',
'http://old.example/media'
);

$this->assertTrue( $p->next_url() );
$this->assertTrue( $p->next_url() );
$this->assertTrue( $p->replace_base_url( 'https://new.example/assets' ) );
$this->assertTrue( $p->set_url( 'https://other.example/file', WPURL::parse( 'https://other.example/file' ) ) );
$this->assertSame(
'<div style="background:url(http://old.example/media/first),url(&quot;https://other.example/file&quot;)"></div>',
$p->get_updated_html()
);
}

/**
* @dataProvider provider_test_css_url_detection
*/
Expand Down
3 changes: 2 additions & 1 deletion components/DataLiberation/Tests/CSSProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,7 @@ public function test_set_token_value_handles_emoji(): void {
}

/**
* Tests that multiple URL values can be updated in the same CSS.
* Tests that multiple URL values can be updated and the last update to a token wins.
*/
public function test_set_token_value_multiple_urls(): void {
$css = 'background: url(old1.jpg); border-image: url(old2.png);';
Expand All @@ -1628,6 +1628,7 @@ public function test_set_token_value_multiple_urls(): void {
if ( 1 === $url_count ) {
$processor->set_token_value( 'new1.jpg' );
} elseif ( 2 === $url_count ) {
$processor->set_token_value( 'discarded.png' );
$processor->set_token_value( 'new2.png' );
}
}
Expand Down
30 changes: 30 additions & 0 deletions components/DataLiberation/Tests/RewriteUrlsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@
use function WordPress\DataLiberation\URL\wp_rewrite_urls;

class RewriteUrlsTest extends TestCase {
public function test_structured_urls_use_the_selected_mapping_base() {
$this->assertSame(
'<a href="/assets/file"></a>',
wp_rewrite_urls(
array(
'block_markup' => '<a href="/root/second/file"></a>',
'base_url' => 'http://old.example/root/first/',
'url-mapping' => array(
'http://old.example/root/first/' => 'https://first.example/first/',
'http://old.example/root/second/' => 'https://second.example/assets/',
),
)
)
);
}

public function test_text_urls_keep_the_configured_base_url_semantics() {
$this->assertSame(
'<p>https://new.example/assets/sub/file</p>',
wp_rewrite_urls(
array(
'block_markup' => '<p>http://old.example/root/sub/file</p>',
'base_url' => 'http://old.example/root/',
'url-mapping' => array(
'http://old.example/root/sub/' => 'https://new.example/assets/',
),
)
)
);
}

/**
*
Expand Down
Loading
Loading