diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 9cdeef75788f2..58bde6ac21a3c 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -2184,7 +2184,23 @@ function path_join( $base, $path ) { * @return string Normalized path. */ function wp_normalize_path( $path ) { - $wrapper = ''; + $path = (string) $path; + + static $hot = array(); + static $warm = array(); + static $max = 100; + + if ( isset( $hot[ $path ] ) ) { + return $hot[ $path ]; + } + + if ( isset( $warm[ $path ] ) ) { + $hot[ $path ] = $warm[ $path ]; + return $hot[ $path ]; + } + + $original_path = $path; + $wrapper = ''; if ( wp_is_stream( $path ) ) { list( $wrapper, $path ) = explode( '://', $path, 2 ); @@ -2203,7 +2219,17 @@ function wp_normalize_path( $path ) { $path = ucfirst( $path ); } - return $wrapper . $path; + $value = $wrapper . $path; + + $hot[ $original_path ] = $value; + + // Rotate segments when hot is full. + if ( count( $hot ) > $max ) { + $warm = $hot; + $hot = array(); + } + + return $value; } /** diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index ccbe24385e14e..f0cce6e44d057 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -222,9 +222,50 @@ public function data_wp_normalize_path() { array( 'php://input', 'php://input' ), array( 'http://example.com//path.ext', 'http://example.com/path.ext' ), array( 'file://c:\\www\\path\\', 'file://C:/www/path/' ), + + // Edge cases. + array( '', '' ), // Empty string should return empty string. + array( 123, '123' ), // Integer should be cast to string. ); } + /** + * Tests that wp_normalize_path() works with objects that have __toString(). + * + * This is important because the function uses a static cache, and the input + * must be cast to string before being used as an array key. + * + * @ticket 33265 + */ + public function test_wp_normalize_path_with_stringable_object() { + $stringable = new class() { + public function __toString() { + return '/var/www/html\\test'; + } + }; + + $this->assertSame( '/var/www/html/test', wp_normalize_path( $stringable ) ); + } + + /** + * Tests that wp_normalize_path() returns consistent results on repeated calls. + * + * The function uses a static cache, so this verifies cache behavior. + * + * @ticket 33265 + */ + public function test_wp_normalize_path_returns_consistent_results() { + $path = 'C:\\www\\path\\'; + + $first_call = wp_normalize_path( $path ); + $second_call = wp_normalize_path( $path ); + $third_call = wp_normalize_path( $path ); + + $this->assertSame( $first_call, $second_call, 'Second call should return same result as first.' ); + $this->assertSame( $second_call, $third_call, 'Third call should return same result as second.' ); + $this->assertSame( 'C:/www/path/', $first_call, 'Normalized path should match expected value.' ); + } + public function test_wp_unique_filename() { $testdir = DIR_TESTDATA . '/images/';