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
25 changes: 24 additions & 1 deletion src/wp-includes/interactivity-api/class-wp-interactivity-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,11 @@ private function data_wp_style_processor( WP_Interactivity_API_Directives_Proces
* merge_style_property( 'background:green;', 'color', 'red' ) => 'background:green;color:red;'
* merge_style_property( 'color:green;', 'color', null ) => ''
*
* Declarations are separated on semicolons without regard for quoted strings or
* `url()` values, so the property being replaced is not recognized when its own
* value contains a semicolon. Resolving that requires tokenizing the declaration
* list rather than splitting it. See #65738.
*
* @param string $style_attribute_value The current style attribute value.
* @param string $style_property_name The style property name to set.
* @param string|false|null $style_property_value The value to set for the style property. With false, null or an
Expand All @@ -1187,7 +1192,25 @@ private function merge_style_property( string $style_attribute_value, string $st
if ( empty( trim( $style_assignment ) ) ) {
continue;
}
list( $name, $value ) = explode( ':', $style_assignment );

/*
* Only the first colon separates a property name from its value; a value may
* legitimately contain more, as in `background-image:url(https://…)`.
*/
$name_and_value = explode( ':', $style_assignment, 2 );

/*
* A segment with no colon is not a declaration. It is most often the remainder
* of a value that was split above by a semicolon inside a quoted string or a
* `url()`, as in `font-family:"Foo;Bar"`. Preserving it verbatim means
* re-joining the segments restores the original value.
*/
if ( ! isset( $name_and_value[1] ) ) {
$result[] = $style_assignment . ';';
continue;
}

list( $name, $value ) = $name_and_value;
if ( trim( $name ) !== $style_property_name ) {
$result[] = trim( $name ) . ':' . trim( $value ) . ';';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,66 @@ public function test_merge_style_property_sets_properties() {
$this->assertSame( 'border:3px solid red;border-style:inset;', $result );
}

/**
* Tests that `merge_style_property` preserves values containing a colon.
*
* Only the first colon in a declaration separates the property name from its
* value, so a value may legitimately contain more of them.
*
* @ticket 63899
*
* @covers ::merge_style_property
*/
public function test_merge_style_property_preserves_values_containing_colons() {
// Keeps an untouched property whose value contains a URL.
$result = $this->merge_style_property( 'background-image:url(https://example.com/a.png)', 'color', 'green' );
$this->assertSame( 'background-image:url(https://example.com/a.png);color:green;', $result );

// Replaces a property whose previous value contained a URL.
$result = $this->merge_style_property( 'background-image:url(https://example.com/a.png)', 'background-image', 'none' );
$this->assertSame( 'background-image:none;', $result );

// Keeps every colon after the first, not only the second.
$result = $this->merge_style_property( '--fallback:url(https://a.test/x.png), url(https://b.test/y.png);', 'color', 'green' );
$this->assertSame( '--fallback:url(https://a.test/x.png), url(https://b.test/y.png);color:green;', $result );
}

/**
* Tests that `merge_style_property` preserves values containing a semicolon.
*
* Declarations are separated on semicolons, so a semicolon inside a quoted
* string or a `url()` splits a value across two segments. The segments must
* re-join to the original value.
*
* @ticket 63899
*
* @covers ::merge_style_property
*/
public function test_merge_style_property_preserves_values_containing_semicolons() {
// Keeps a quoted value containing a semicolon.
$result = $this->merge_style_property( 'font-family:"Foo;Bar",serif', 'color', 'green' );
$this->assertSame( 'font-family:"Foo;Bar",serif;color:green;', $result );

// Keeps a data URI, which contains both a colon and a semicolon.
$result = $this->merge_style_property( 'background-image:url(data:image/gif;base64,R0lGODlhAQ)', 'color', 'green' );
$this->assertSame( 'background-image:url(data:image/gif;base64,R0lGODlhAQ);color:green;', $result );
}

/**
* Tests that `merge_style_property` handles segments that contain no colon.
*
* Malformed input previously raised "Undefined array key 1" and a `trim()`
* deprecation notice, and emitted a stray colon into the result.
*
* @ticket 63899
*
* @covers ::merge_style_property
*/
public function test_merge_style_property_handles_segments_without_a_colon() {
$result = $this->merge_style_property( 'translate:none;translate3d(0px, 0px, 0px);', 'color', 'green' );
$this->assertSame( 'translate:none;translate3d(0px, 0px, 0px);color:green;', $result );
}

/**
* Tests that `merge_style_property` works correctly with falsy values,
* removing or ignoring them as appropriate.
Expand Down Expand Up @@ -234,6 +294,20 @@ public function test_wp_style_sets_style_property_when_style_attribute_exists()
$this->assertSame( 'padding:10px;color:green;', $p->get_attribute( 'style' ) );
}

/**
* Tests that the `data-wp-style` directive leaves an existing style attribute
* intact when one of its values contains a colon.
*
* @ticket 63899
*
* @covers ::process_directives
*/
public function test_wp_style_preserves_existing_style_attribute_containing_a_url() {
$html = '<div style="background-image:url(https://example.com/a.png)" data-wp-style--color="myPlugin::state.green">Text</div>';
list($p) = $this->process_directives( $html );
$this->assertSame( 'background-image:url(https://example.com/a.png);color:green;', $p->get_attribute( 'style' ) );
}

/**
* Tests that the `data-wp-style` directive overwrites an existing style
* property with a new value.
Expand Down
Loading