diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php
index 20790d1c44..3da5bf2624 100644
--- a/src/wp-includes/kses.php
+++ b/src/wp-includes/kses.php
@@ -1076,6 +1076,7 @@ function wp_kses_attr( $element, $attr, $allowed_html, $allowed_protocols ) {
* Determines whether an attribute is allowed.
*
* @since 4.2.3
+ * @since 5.0.0 Add support for `data-*` wildcard attributes.
*
* @param string $name The attribute name. Passed by reference. Returns empty string when not allowed.
* @param string $value The attribute value. Passed by reference. Returns a filtered value.
@@ -1090,8 +1091,26 @@ function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowe
$name_low = strtolower( $name );
if ( ! isset( $allowed_attr[ $name_low ] ) || '' == $allowed_attr[ $name_low ] ) {
- $name = $value = $whole = '';
- return false;
+ /*
+ * Allow `data-*` attributes.
+ *
+ * When specifying `$allowed_html`, the attribute name should be set as
+ * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
+ * https://www.w3.org/TR/html40/struct/objects.html#adef-data).
+ *
+ * Note: the attribute name should only contain `A-Za-z0-9_-` chars,
+ * double hyphens `--` are not accepted by WordPress.
+ */
+ if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] ) && preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match ) ) {
+ /*
+ * Add the whole attribute name to the allowed attributes and set any restrictions
+ * for the `data-*` attribute values for the current element.
+ */
+ $allowed_attr[ $match[0] ] = $allowed_attr['data-*'];
+ } else {
+ $name = $value = $whole = '';
+ return false;
+ }
}
if ( 'style' == $name_low ) {
@@ -2091,6 +2110,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
* Helper function to add global attributes to a tag in the allowed html list.
*
* @since 3.5.0
+ * @since 5.0.0 Add support for `data-*` wildcard attributes.
* @access private
* @ignore
*
@@ -2099,11 +2119,12 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
*/
function _wp_add_global_attributes( $value ) {
$global_attributes = array(
- 'class' => true,
- 'id' => true,
- 'style' => true,
- 'title' => true,
- 'role' => true,
+ 'class' => true,
+ 'id' => true,
+ 'style' => true,
+ 'title' => true,
+ 'role' => true,
+ 'data-*' => true,
);
if ( true === $value ) {
diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php
index 5ae0101dab..b91fe5d386 100644
--- a/tests/phpunit/tests/kses.php
+++ b/tests/phpunit/tests/kses.php
@@ -825,4 +825,99 @@ EOF;
),
);
}
+
+ /**
+ * Data attributes are globally accepted.
+ *
+ * @ticket 33121
+ */
+ function test_wp_kses_attr_data_attribute_is_allowed() {
+ $test = '
Pens and pencils
';
+ $expected = 'Pens and pencils
';
+
+ $this->assertEquals( $expected, wp_kses_post( $test ) );
+ }
+
+ /**
+ * Ensure wildcard attributes block unprefixed wildcard uses.
+ *
+ * @ticket 33121
+ */
+ function test_wildcard_requires_hyphen_after_prefix() {
+ $allowed_html = array(
+ 'div' => array(
+ 'data-*' => true,
+ 'on-*' => true,
+ ),
+ );
+
+ $string = 'Malformed attributes
';
+ $expected = 'Malformed attributes
';
+
+ $actual = wp_kses( $string, $allowed_html );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * Ensure wildcard allows two hyphen.
+ *
+ * @ticket 33121
+ */
+ function test_wildcard_allows_two_hyphens() {
+ $allowed_html = array(
+ 'div' => array(
+ 'data-*' => true,
+ ),
+ );
+
+ $string = 'Well formed attribute
';
+ $expected = 'Well formed attribute
';
+
+ $actual = wp_kses( $string, $allowed_html );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * Ensure wildcard attributes only support valid prefixes.
+ *
+ * @dataProvider data_wildcard_attribute_prefixes
+ *
+ * @ticket 33121
+ */
+ function test_wildcard_attribute_prefixes( $wildcard_attribute, $expected ) {
+ $allowed_html = array(
+ 'div' => array(
+ $wildcard_attribute => true,
+ ),
+ );
+
+ $name = str_replace( '*', strtolower( __FUNCTION__ ), $wildcard_attribute );
+ $value = __FUNCTION__;
+ $whole = "{$name}=\"{$value}\"";
+
+ $actual = wp_kses_attr_check( $name, $value, $whole, 'n', 'div', $allowed_html );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * @return array Array of arguments for wildcard testing
+ * [0] The prefix being tested.
+ * [1] The outcome of `wp_kses_attr_check` for the prefix.
+ */
+ function data_wildcard_attribute_prefixes() {
+ return array(
+ // Ends correctly
+ array( 'data-*', true ),
+
+ // Does not end with trialing `-`.
+ array( 'data*', false ),
+
+ // Multiple wildcards.
+ array( 'd*ta-*', false ),
+ array( 'data**', false ),
+ );
+ }
}
diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php
index 465b58bf93..70ea949a8a 100644
--- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php
+++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php
@@ -1079,9 +1079,9 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control
array(
// Raw values.
array(
- 'title' => 'link',
- 'description' => 'link',
- 'caption' => 'link',
+ 'title' => 'link',
+ 'description' => 'link',
+ 'caption' => 'link',
),
// Expected returned values.
array(
diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php
index eef8bc85dc..ebb2d507dd 100644
--- a/tests/phpunit/tests/rest-api/rest-posts-controller.php
+++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php
@@ -3283,9 +3283,9 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
array(
// Raw values.
array(
- 'title' => 'link',
- 'content' => 'link',
- 'excerpt' => 'link',
+ 'title' => 'link',
+ 'content' => 'link',
+ 'excerpt' => 'link',
),
// Expected returned values.
array(