Skip to content

Commit 274dd64

Browse files
authored
Merge branch 'main' into copilot/fix-core-install-home-siteurl
2 parents c26a796 + 300001f commit 274dd64

File tree

6 files changed

+297
-15
lines changed

6 files changed

+297
-15
lines changed

.gitattributes

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/.actrc export-ignore
2+
/.distignore export-ignore
3+
/.editorconfig export-ignore
4+
/.github export-ignore
5+
/.gitignore export-ignore
6+
/.typos.toml export-ignore
7+
/AGENTS.md export-ignore
8+
/behat.yml export-ignore
9+
/features export-ignore
10+
/phpcs.xml.dist export-ignore
11+
/phpstan.neon.dist export-ignore
12+
/phpunit.xml.dist export-ignore
13+
/tests export-ignore
14+
/wp-cli.yml export-ignore

.github/workflows/copilot-setup-steps.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
- name: Set up PHP environment
2727
if: steps.check_composer_file.outputs.files_exists == 'true'
28-
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2
28+
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2
2929
with:
3030
php-version: 'latest'
3131
ini-values: zend.assertions=1, error_reporting=-1, display_errors=On
@@ -36,7 +36,7 @@ jobs:
3636

3737
- name: Install Composer dependencies & cache dependencies
3838
if: steps.check_composer_file.outputs.files_exists == 'true'
39-
uses: ramsey/composer-install@a35c6ebd3d08125aaf8852dff361e686a1a67947 # v3
39+
uses: ramsey/composer-install@65e4f84970763564f46a70b8a54b90d033b3bdda # v3
4040
env:
4141
COMPOSER_ROOT_VERSION: dev-${{ github.event.repository.default_branch }}
4242
with:

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ update isn't actually running.
419419
Downloading update from https://downloads.wordpress.org/release/wordpress-4.5.2-no-content.zip...
420420
Unpacking the update...
421421
Cleaning up files...
422-
No files found that need cleaning up
422+
No old files were removed.
423423
Success: WordPress updated successfully.
424424

425425
# Update WordPress using zip file.
@@ -433,7 +433,9 @@ update isn't actually running.
433433
Updating to version 3.1 (en_US)...
434434
Downloading update from https://wordpress.org/wordpress-3.1.zip...
435435
Unpacking the update...
436-
Warning: Checksums not available for WordPress 3.1/en_US. Please cleanup files manually.
436+
Cleaning up files...
437+
No old files were removed.
438+
Warning: Could not retrieve WordPress core checksums; skipping checksum-based cleanup. Files listed in $_old_files were still cleaned up.
437439
Success: WordPress updated successfully.
438440

439441

features/core-download.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ Feature: Download WordPress
180180
"""
181181
Failed to find WordPress version
182182
"""
183-
And STDERR should contain:
183+
And STDERR should not contain:
184184
"""
185185
Warning: Checksums not available for WordPress nightly/en_US. Please cleanup files manually.
186186
"""

features/core-update.feature

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Feature: Update WordPress core
2121
"""
2222
Starting update...
2323
Unpacking the update...
24+
Cleaning up files...
25+
No old files were removed.
2426
Success: WordPress updated successfully.
2527
"""
2628

@@ -420,6 +422,90 @@ Feature: Update WordPress core
420422
</div>
421423
"""
422424

425+
Scenario: Old files from $_old_files are cleaned up when upgrading
426+
Given a WP install
427+
428+
When I run `wp core download --version=6.8 --force`
429+
430+
# Create files that should be removed according to 6.9 old_files list
431+
Given a wp-includes/blocks/post-author/editor.css file:
432+
"""
433+
/* Old CSS file */
434+
"""
435+
And a wp-includes/blocks/post-author/editor.min.css file:
436+
"""
437+
/* Old minified CSS */
438+
"""
439+
And a wp-includes/blocks/post-author/editor-rtl.css file:
440+
"""
441+
/* Old RTL CSS */
442+
"""
443+
And a wp-includes/blocks/post-author/editor-rtl.min.css file:
444+
"""
445+
/* Old RTL minified CSS */
446+
"""
447+
And a wp-includes/SimplePie/src/Core.php file:
448+
"""
449+
<?php
450+
// Old SimplePie Core file
451+
"""
452+
And an empty wp-includes/SimplePie/src/Decode directory
453+
454+
When I run `wp core update --version=6.9 --force`
455+
Then STDOUT should contain:
456+
"""
457+
Success: WordPress updated successfully.
458+
"""
459+
And the wp-includes/blocks/post-author/editor.css file should not exist
460+
And the wp-includes/blocks/post-author/editor.min.css file should not exist
461+
And the wp-includes/blocks/post-author/editor-rtl.css file should not exist
462+
And the wp-includes/blocks/post-author/editor-rtl.min.css file should not exist
463+
And the wp-includes/SimplePie/src/Core.php file should not exist
464+
And the wp-includes/SimplePie/src/Decode directory should not exist
465+
466+
@require-php-7.2
467+
Scenario: Old files cleanup works when checksums unavailable
468+
Given a WP install
469+
470+
When I run `wp core download --version=6.8 --force`
471+
Then STDOUT should contain:
472+
"""
473+
Success: WordPress downloaded.
474+
"""
475+
476+
# Create files that exist in the $_old_files list from WordPress 6.9
477+
Given a wp-includes/blocks/post-author/editor.css file:
478+
"""
479+
/* Old CSS file */
480+
"""
481+
And a wp-includes/blocks/post-author/editor.min.css file:
482+
"""
483+
/* Old minified CSS */
484+
"""
485+
486+
# Mock checksum API to return empty response so checksums are unavailable
487+
And that HTTP requests to https://api.wordpress.org/core/checksums/1.0/ will respond with:
488+
"""
489+
HTTP/1.1 200
490+
Content-Type: application/json
491+
492+
{}
493+
"""
494+
495+
When I try `wp core update --version=6.9 --force`
496+
Then STDOUT should contain:
497+
"""
498+
Cleaning up files...
499+
"""
500+
And STDOUT should contain:
501+
"""
502+
Success: WordPress updated successfully.
503+
"""
504+
505+
# Verify files from $_old_files were removed
506+
And the wp-includes/blocks/post-author/editor.css file should not exist
507+
And the wp-includes/blocks/post-author/editor.min.css file should not exist
508+
423509
Scenario: Update WordPress locale without --force when version is the same
424510
Given a WP install
425511
And an empty cache
@@ -485,7 +571,7 @@ Feature: Update WordPress core
485571
"""
486572
Package language: en_US
487573
"""
488-
@require-php-7.0 @require-wp-6.1
574+
@require-wp-6.1
489575
Scenario: Attempting to downgrade without --force shows helpful message
490576
Given a WP install
491577

src/Core_Command.php

Lines changed: 189 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,7 +1168,7 @@ private static function get_core_checksums( $version, $locale, $insecure ) {
11681168
* Downloading update from https://downloads.wordpress.org/release/wordpress-4.5.2-no-content.zip...
11691169
* Unpacking the update...
11701170
* Cleaning up files...
1171-
* No files found that need cleaning up
1171+
* No old files were removed.
11721172
* Success: WordPress updated successfully.
11731173
*
11741174
* # Update WordPress using zip file.
@@ -1182,7 +1182,9 @@ private static function get_core_checksums( $version, $locale, $insecure ) {
11821182
* Updating to version 3.1 (en_US)...
11831183
* Downloading update from https://wordpress.org/wordpress-3.1.zip...
11841184
* Unpacking the update...
1185-
* Warning: Checksums not available for WordPress 3.1/en_US. Please cleanup files manually.
1185+
* Cleaning up files...
1186+
* No old files were removed.
1187+
* Warning: Could not retrieve WordPress core checksums; skipping checksum-based cleanup. Files listed in $_old_files were still cleaned up.
11861188
* Success: WordPress updated successfully.
11871189
*
11881190
* @alias upgrade
@@ -1839,16 +1841,16 @@ private function cleanup_extra_files( $version_from, $version_to, $locale, $inse
18391841
return;
18401842
}
18411843

1842-
$old_checksums = self::get_core_checksums( $version_from, $locale ?: 'en_US', $insecure );
1843-
if ( ! is_array( $old_checksums ) ) {
1844-
WP_CLI::warning( "{$old_checksums} Please cleanup files manually." );
1845-
return;
1846-
}
1844+
// Always clean up files from WordPress core's $_old_files list first
1845+
$this->cleanup_old_files();
18471846

1847+
$old_checksums = self::get_core_checksums( $version_from, $locale ?: 'en_US', $insecure );
18481848
$new_checksums = self::get_core_checksums( $version_to, $locale ?: 'en_US', $insecure );
1849-
if ( ! is_array( $new_checksums ) ) {
1850-
WP_CLI::warning( "{$new_checksums} Please cleanup files manually." );
18511849

1850+
$has_checksums = is_array( $old_checksums ) && is_array( $new_checksums );
1851+
1852+
if ( ! $has_checksums ) {
1853+
WP_CLI::warning( 'Could not retrieve WordPress core checksums; skipping checksum-based cleanup. Files listed in $_old_files were still cleaned up.' );
18521854
return;
18531855
}
18541856

@@ -1944,6 +1946,184 @@ private function cleanup_extra_files( $version_from, $version_to, $locale, $inse
19441946
}
19451947
}
19461948

1949+
/**
1950+
* Clean up old files using WordPress core's $_old_files list.
1951+
*
1952+
* It unconditionally deletes files from the $_old_files global array maintained by WordPress core.
1953+
*/
1954+
private function cleanup_old_files() {
1955+
$old_files = $this->get_old_files_list();
1956+
if ( empty( $old_files ) ) {
1957+
WP_CLI::log( 'No files found that need cleaning up.' );
1958+
return;
1959+
}
1960+
1961+
WP_CLI::log( 'Cleaning up files...' );
1962+
1963+
$count = $this->remove_old_files_from_list( $old_files );
1964+
1965+
if ( $count ) {
1966+
WP_CLI::log( number_format( $count ) . ' files cleaned up.' );
1967+
} else {
1968+
WP_CLI::log( 'No old files were removed.' );
1969+
}
1970+
}
1971+
1972+
/**
1973+
* Get the list of old files from WordPress core.
1974+
*
1975+
* @return array Array of old file paths, or empty array if not available.
1976+
*/
1977+
private function get_old_files_list() {
1978+
// Include WordPress core's update file to access the $_old_files list
1979+
if ( ! file_exists( ABSPATH . 'wp-admin/includes/update-core.php' ) ) {
1980+
WP_CLI::warning( 'Could not find update-core.php. Please cleanup files manually.' );
1981+
return array();
1982+
}
1983+
1984+
require_once ABSPATH . 'wp-admin/includes/update-core.php';
1985+
1986+
global $_old_files;
1987+
1988+
if ( empty( $_old_files ) || ! is_array( $_old_files ) ) {
1989+
return array();
1990+
}
1991+
1992+
return $_old_files;
1993+
}
1994+
1995+
/**
1996+
* Remove old files from a list.
1997+
*
1998+
* This is a shared helper method that handles the actual removal of files and directories.
1999+
*
2000+
* @param array $files Array of file paths to remove.
2001+
* @return int Number of files/directories successfully removed.
2002+
*/
2003+
private function remove_old_files_from_list( $files ) {
2004+
$count = 0;
2005+
2006+
$abspath_realpath = realpath( ABSPATH );
2007+
if ( false === $abspath_realpath ) {
2008+
WP_CLI::debug( 'Failed to resolve ABSPATH realpath', 'core' );
2009+
return $count;
2010+
}
2011+
$abspath_realpath_trailing = Utils\trailingslashit( $abspath_realpath );
2012+
2013+
foreach ( $files as $file ) {
2014+
$file_path = ABSPATH . $file;
2015+
2016+
// Skip entries that don't exist and aren't (broken) symlinks.
2017+
if ( ! file_exists( $file_path ) && ! is_link( $file_path ) ) {
2018+
continue;
2019+
}
2020+
2021+
// Symlinks: validate and remove without following the link.
2022+
if ( is_link( $file_path ) ) {
2023+
$normalized_path = realpath( dirname( $file_path ) );
2024+
if ( false === $normalized_path
2025+
|| 0 !== strpos( Utils\trailingslashit( $normalized_path ), $abspath_realpath_trailing )
2026+
) {
2027+
WP_CLI::debug( "Skipping symbolic link outside of ABSPATH: {$file}", 'core' );
2028+
continue;
2029+
}
2030+
if ( unlink( $file_path ) ) {
2031+
WP_CLI::log( "Symbolic link removed: {$file}" );
2032+
++$count;
2033+
} else {
2034+
WP_CLI::debug( "Failed to remove symbolic link: {$file}", 'core' );
2035+
}
2036+
continue;
2037+
}
2038+
2039+
// Regular files/directories: validate real path is within ABSPATH.
2040+
$file_realpath = realpath( $file_path );
2041+
if ( false === $file_realpath || 0 !== strpos( Utils\trailingslashit( $file_realpath ), $abspath_realpath_trailing ) ) {
2042+
WP_CLI::debug( "Skipping file outside of ABSPATH: {$file}", 'core' );
2043+
continue;
2044+
}
2045+
2046+
if ( is_dir( $file_path ) ) {
2047+
if ( $this->remove_directory( $file_path, $abspath_realpath_trailing ) ) {
2048+
WP_CLI::log( "Directory removed: {$file}" );
2049+
++$count;
2050+
} else {
2051+
WP_CLI::debug( "Failed to remove directory: {$file}", 'core' );
2052+
}
2053+
} elseif ( unlink( $file_path ) ) {
2054+
WP_CLI::log( "File removed: {$file}" );
2055+
++$count;
2056+
} else {
2057+
WP_CLI::debug( "Failed to remove file: {$file}", 'core' );
2058+
}
2059+
}
2060+
2061+
return $count;
2062+
}
2063+
2064+
/**
2065+
* Recursively remove a directory and its contents.
2066+
*
2067+
* @param string $dir Directory path to remove.
2068+
* @param string $abspath_realpath_trailing Cached ABSPATH realpath with trailing slash for performance.
2069+
* @return bool True on success, false on failure.
2070+
*/
2071+
private function remove_directory( $dir, $abspath_realpath_trailing ) {
2072+
$dir_realpath = realpath( $dir );
2073+
if ( false === $dir_realpath ) {
2074+
WP_CLI::debug( "Failed to resolve realpath for directory: {$dir}", 'core' );
2075+
return false;
2076+
}
2077+
if ( 0 !== strpos( Utils\trailingslashit( $dir_realpath ), $abspath_realpath_trailing ) ) {
2078+
WP_CLI::debug( "Attempted to remove directory outside of ABSPATH: {$dir_realpath}", 'core' );
2079+
return false;
2080+
}
2081+
if ( ! is_dir( $dir ) ) {
2082+
return false;
2083+
}
2084+
2085+
$files = new RecursiveIteratorIterator(
2086+
new RecursiveDirectoryIterator( $dir, RecursiveDirectoryIterator::SKIP_DOTS ),
2087+
RecursiveIteratorIterator::CHILD_FIRST
2088+
);
2089+
2090+
/** @var \SplFileInfo $fileinfo */
2091+
foreach ( $files as $fileinfo ) {
2092+
// Use the symlink's own path (not realpath) to avoid following it outside ABSPATH.
2093+
if ( $fileinfo->isLink() ) {
2094+
$path = $fileinfo->getPathname();
2095+
if ( ! unlink( $path ) ) {
2096+
WP_CLI::debug( "Failed to remove symbolic link: {$path}", 'core' );
2097+
return false;
2098+
}
2099+
continue;
2100+
}
2101+
2102+
$path = $fileinfo->getRealPath();
2103+
if ( false === $path || 0 !== strpos( $path, $abspath_realpath_trailing ) ) {
2104+
WP_CLI::debug( "Attempted to remove path outside of ABSPATH: {$path}", 'core' );
2105+
return false;
2106+
}
2107+
2108+
if ( $fileinfo->isDir() ) {
2109+
if ( ! rmdir( $path ) ) {
2110+
WP_CLI::debug( "Failed to remove directory: {$path}", 'core' );
2111+
return false;
2112+
}
2113+
} elseif ( ! unlink( $path ) ) {
2114+
WP_CLI::debug( "Failed to remove file: {$path}", 'core' );
2115+
return false;
2116+
}
2117+
}
2118+
2119+
if ( ! rmdir( $dir ) ) {
2120+
WP_CLI::debug( "Failed to remove directory: {$dir}", 'core' );
2121+
return false;
2122+
}
2123+
2124+
return true;
2125+
}
2126+
19472127
private static function strip_content_dir( $zip_file ) {
19482128
$new_zip_file = Utils\get_temp_dir() . uniqid( 'wp_' ) . '.zip';
19492129
register_shutdown_function(

0 commit comments

Comments
 (0)