diff --git a/src/wp-includes/class-wp-rewrite.php b/src/wp-includes/class-wp-rewrite.php index 8b75fa5c36d16..9cc69a68aebb3 100644 --- a/src/wp-includes/class-wp-rewrite.php +++ b/src/wp-includes/class-wp-rewrite.php @@ -1844,7 +1844,13 @@ public function add_permastruct( $name, $struct, $args = array() ) { $struct = $this->root . $struct; } - $args['struct'] = $struct; + $args['struct'] = preg_replace_callback( + '/(?:%[0-9A-F]{2})+/', + static function ( $matches ) { + return rawurldecode( $matches[0] ); + }, + $struct + ); $this->extra_permastructs[ $name ] = $args; } diff --git a/tests/phpunit/tests/rewrite/permastructs.php b/tests/phpunit/tests/rewrite/permastructs.php index ce98e06a68bb3..db4361b3c0059 100644 --- a/tests/phpunit/tests/rewrite/permastructs.php +++ b/tests/phpunit/tests/rewrite/permastructs.php @@ -14,6 +14,13 @@ public function set_up() { $this->set_permalink_structure( '/%postname%/' ); } + public function tear_down() { + remove_permastruct( 'wptests_cert' ); + unregister_taxonomy( 'wptests_cert' ); + + parent::tear_down(); + } + public function test_add_permastruct() { global $wp_rewrite; @@ -31,6 +38,7 @@ public function test_add_permastruct() { ), $wp_rewrite->extra_permastructs['foo'] ); + remove_permastruct( 'foo' ); } public function test_remove_permastruct() { @@ -43,4 +51,34 @@ public function test_remove_permastruct() { remove_permastruct( 'foo' ); $this->assertArrayNotHasKey( 'foo', $wp_rewrite->extra_permastructs ); } + + /** + * Tests that a URL-encoded Unicode taxonomy slug is decoded in the permastruct and + * that the generated rewrite rules correctly map the taxonomy query var to $matches[1]. + * + * @ticket 41791 + */ + public function test_add_permastruct_with_url_encoded_unicode_slug() { + global $wp_rewrite; + + register_taxonomy( + 'wptests_cert', + 'post', + array( + 'rewrite' => array( + 'slug' => urlencode( 'Сертификат' ), + ), + ) + ); + + $stored_struct = $wp_rewrite->extra_permastructs['wptests_cert']['struct']; + + // The struct must store decoded Unicode, not percent-encoded sequences. + $this->assertStringContainsString( 'Сертификат', $stored_struct ); + $this->assertStringNotContainsString( urlencode( 'Сертификат' ), $stored_struct ); + + // The generated rules must map the taxonomy var to $matches[1], not a shifted index. + $rules = $wp_rewrite->generate_rewrite_rules( $stored_struct ); + $this->assertContains( 'index.php?wptests_cert=$matches[1]', array_values( $rules ) ); + } }