Skip to content

Commit d716bfd

Browse files
Themes: Normalize Windows backslashes in theme directory paths.
Use wp_normalize_path() in register_theme_directory() and search_theme_directories() so that paths are always stored and compared with forward slashes. This prevents a White Screen when a database dump created on Windows is loaded on a Unix system, where the stored backslash paths no longer match WP_CONTENT_DIR. Fixes #29051
1 parent db24a90 commit d716bfd

2 files changed

Lines changed: 56 additions & 3 deletions

File tree

src/wp-includes/theme.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,15 @@ function register_theme_directory( $directory ) {
428428
$wp_theme_directories = array();
429429
}
430430

431-
$untrailed = untrailingslashit( $directory );
431+
/*
432+
* Normalize the path so that Windows backslashes are converted to forward
433+
* slashes. This ensures consistent path storage and comparison across
434+
* operating systems, preventing issues when a database dump made on Windows
435+
* is loaded on a Unix-based system (and vice versa).
436+
*
437+
* See https://core.trac.wordpress.org/ticket/29051
438+
*/
439+
$untrailed = untrailingslashit( wp_normalize_path( $directory ) );
432440
if ( ! empty( $untrailed ) && ! in_array( $untrailed, $wp_theme_directories, true ) ) {
433441
$wp_theme_directories[] = $untrailed;
434442
}
@@ -469,8 +477,17 @@ function search_theme_directories( $force = false ) {
469477
* to use in get_theme_root().
470478
*/
471479
foreach ( $wp_theme_directories as $theme_root ) {
472-
if ( str_starts_with( $theme_root, WP_CONTENT_DIR ) ) {
473-
$relative_theme_roots[ str_replace( WP_CONTENT_DIR, '', $theme_root ) ] = $theme_root;
480+
/*
481+
* Normalize the theme root path and the content directory to forward slashes
482+
* so that the prefix strip works correctly on Windows, where directory
483+
* separators may differ from those stored in WP_CONTENT_DIR.
484+
*
485+
* See https://core.trac.wordpress.org/ticket/29051
486+
*/
487+
$normalized_theme_root = wp_normalize_path( $theme_root );
488+
$normalized_content_dir = wp_normalize_path( WP_CONTENT_DIR );
489+
if ( str_starts_with( $normalized_theme_root, $normalized_content_dir ) ) {
490+
$relative_theme_roots[ str_replace( $normalized_content_dir, '', $normalized_theme_root ) ] = $theme_root;
474491
} else {
475492
$relative_theme_roots[ $theme_root ] = $theme_root;
476493
}

tests/phpunit/tests/theme/themeDir.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,40 @@ public function test_theme_dir_slashes() {
319319
rmdir( WP_CONTENT_DIR . '/themes/foo' );
320320
rmdir( WP_CONTENT_DIR . '/themes/foo-themes' );
321321
}
322+
323+
/**
324+
* @ticket 29051
325+
*/
326+
public function test_register_theme_directory_stores_normalized_path() {
327+
@mkdir( WP_CONTENT_DIR . '/themes/custom-win-dir', 0777, true );
328+
329+
$dir = WP_CONTENT_DIR . '/themes/custom-win-dir';
330+
register_theme_directory( $dir );
331+
332+
// Stored path must equal wp_normalize_path() output (no backslashes on any OS).
333+
$stored = end( $GLOBALS['wp_theme_directories'] );
334+
$this->assertSame( wp_normalize_path( $dir ), $stored );
335+
$this->assertStringNotContainsString( '\\', $stored, 'Backslashes must not appear in registered theme dirs.' );
336+
337+
rmdir( WP_CONTENT_DIR . '/themes/custom-win-dir' );
338+
}
339+
340+
/**
341+
* @ticket 29051
342+
*/
343+
public function test_search_theme_directories_finds_themes_under_content_dir() {
344+
// self::THEME_ROOT is outside WP_CONTENT_DIR — themes there must still be found.
345+
$results = search_theme_directories( true );
346+
347+
$this->assertIsArray( $results );
348+
349+
// Every result must carry a 'theme_root' key.
350+
foreach ( $results as $slug => $data ) {
351+
$this->assertArrayHasKey( 'theme_root', $data, "theme_root missing for slug: $slug" );
352+
}
353+
354+
// At least one theme from self::THEME_ROOT must be present.
355+
$roots = array_column( $results, 'theme_root' );
356+
$this->assertContains( self::THEME_ROOT, $roots, 'Themes from non-content-dir root must be discoverable.' );
357+
}
322358
}

0 commit comments

Comments
 (0)