Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
41efc3a
Initial plan
Copilot Nov 7, 2025
baee2fa
Add support for cleaning up old files using WordPress $_old_files list
Copilot Nov 7, 2025
37cfece
Enhance cleanup to use $_old_files even when checksums are available
Copilot Nov 7, 2025
4760835
Update tests and documentation to reflect new cleanup behavior
Copilot Nov 7, 2025
5a094a0
Address code review feedback - improve error handling and reduce dupl…
Copilot Nov 7, 2025
bf5a413
Remove @ error suppression operators per code review feedback
Copilot Nov 7, 2025
fe5a714
Use directory_separator
swissspidy Nov 7, 2025
12fd04b
Path validation
swissspidy Nov 7, 2025
d5ffb6d
Adjust message
swissspidy Nov 7, 2025
96fcab2
Refactor to reduce code duplication and add symlink handling
Copilot Nov 7, 2025
038d662
Add path validation and improve security checks
Copilot Nov 7, 2025
92f2049
Fix path validation logic for better security
Copilot Nov 7, 2025
0d1bfb3
Optimize path validation by caching ABSPATH realpath
Copilot Nov 7, 2025
6b30e73
Clean up code style - remove trailing whitespace and simplify logic
Copilot Nov 7, 2025
831e197
Lint & test fixes
swissspidy Nov 10, 2025
1a5cb90
Merge branch 'main' into copilot/add-delete-old-core-files
swissspidy Dec 7, 2025
0064498
Add comprehensive test coverage for old files cleanup functionality
Copilot Dec 7, 2025
253a873
Apply suggestions from code review
swissspidy Dec 7, 2025
40c7fc5
Remove custom old_files test and improve checksums unavailable test w…
Copilot Dec 8, 2025
8ab689b
Gherkin lint fix
swissspidy Dec 8, 2025
f4554f6
Merge branch 'main' into copilot/add-delete-old-core-files
swissspidy Jan 20, 2026
29fcb9d
Update src/Core_Command.php
swissspidy Jan 20, 2026
deb6133
Fix merge hiccup
swissspidy Jan 20, 2026
2217d6a
Merge branch 'main' into copilot/add-delete-old-core-files
swissspidy Jan 20, 2026
759bdd0
Add symlink handling and optimize remove_directory with cached parameter
Copilot Jan 20, 2026
7d21edb
Improve log messages for symbolic link removal
Copilot Jan 20, 2026
0c04d93
Fix symlink security validation to check link path not target
Copilot Jan 20, 2026
050a066
Simplify symlink validation to check path directly without resolution
Copilot Jan 20, 2026
0366f61
Fix symlink validation with proper path normalization
Copilot Jan 20, 2026
463a2d7
Merge branch 'main' into copilot/add-delete-old-core-files
swissspidy Feb 17, 2026
71a888f
Apply suggestion from @swissspidy
swissspidy Mar 11, 2026
6443609
Merge branch 'main' into copilot/add-delete-old-core-files
swissspidy Mar 11, 2026
d1e030f
Change order: clean up old_files first, then handle checksums
Copilot Mar 11, 2026
3f4f923
Update test to include new cleanup log messages in ZIP update scenario
Copilot Mar 11, 2026
d030292
Update features/core-update.feature
swissspidy Mar 11, 2026
17a6032
Update src/Core_Command.php
swissspidy Mar 11, 2026
ce9a726
Short-circuit early in remove_old_files_from_list() before expensive …
Copilot Mar 12, 2026
66018df
Simplify remove_old_files_from_list by merging validation and removal…
Copilot Mar 12, 2026
82a15ed
Simplify remove_directory using RecursiveDirectoryIterator
Copilot Mar 12, 2026
059e826
Apply suggestion from @swissspidy
swissspidy Mar 12, 2026
5eeb1af
Apply suggestions from code review
swissspidy Mar 12, 2026
41c4830
Lint fix
swissspidy Mar 12, 2026
60b6bb2
Update docblock examples to reflect new cleanup output messages
Copilot Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion features/core-download.feature
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Feature: Download WordPress
"""
Failed to find WordPress version
"""
And STDERR should contain:
And STDERR should not contain:
"""
Warning: Checksums not available for WordPress nightly/en_US. Please cleanup files manually.
"""
Expand Down
202 changes: 194 additions & 8 deletions src/Core_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@
* Updating to version 3.1 (en_US)...
* Downloading update from https://wordpress.org/wordpress-3.1.zip...
* Unpacking the update...
* Warning: Checksums not available for WordPress 3.1/en_US. Please cleanup files manually.
* Cleaning up files...
* Success: WordPress updated successfully.
Comment thread
swissspidy marked this conversation as resolved.
*
* @alias upgrade
Expand Down Expand Up @@ -1511,15 +1511,13 @@
}

$old_checksums = self::get_core_checksums( $version_from, $locale ?: 'en_US', $insecure );
if ( ! is_array( $old_checksums ) ) {
WP_CLI::warning( "{$old_checksums} Please cleanup files manually." );
return;
}

$new_checksums = self::get_core_checksums( $version_to, $locale ?: 'en_US', $insecure );
if ( ! is_array( $new_checksums ) ) {
WP_CLI::warning( "{$new_checksums} Please cleanup files manually." );

$has_checksums = is_array( $old_checksums ) && is_array( $new_checksums );

if ( ! $has_checksums ) {
Comment thread
swissspidy marked this conversation as resolved.
// When checksums are not available, use WordPress core's $_old_files list
$this->cleanup_old_files();
Comment thread
swissspidy marked this conversation as resolved.
Outdated
return;
}

Expand Down Expand Up @@ -1613,6 +1611,194 @@
WP_CLI::log( 'No files found that need cleaning up.' );
}
}

// Additionally, clean up files from $_old_files that are not in checksums
// These should be deleted unconditionally as they are known old files
$this->cleanup_old_files_not_in_checksums( $old_checksums, $new_checksums );
}

/**
* Clean up old files using WordPress core's $_old_files list.
*
* This method is used when checksums are not available for version comparison.
* It unconditionally deletes files from the $_old_files global array maintained by WordPress core.
Comment thread
swissspidy marked this conversation as resolved.
*/
private function cleanup_old_files() {
$old_files = $this->get_old_files_list();
if ( empty( $old_files ) ) {
WP_CLI::log( 'No files found that need cleaning up.' );
return;
}

WP_CLI::log( 'Cleaning up files...' );

$count = 0;
foreach ( $old_files as $file ) {
// wp-content should be considered user data
if ( 0 === stripos( $file, 'wp-content' ) ) {
continue;
}

$file_path = ABSPATH . $file;

// Handle both files and directories
if ( file_exists( $file_path ) ) {
if ( is_dir( $file_path ) ) {
// Remove directory recursively
if ( $this->remove_directory( $file_path ) ) {
WP_CLI::log( "Directory removed: {$file}" );
++$count;
} else {
WP_CLI::debug( "Failed to remove directory: {$file}", 'core' );
}
} else {

Check failure on line 1654 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

If control structure block found as the only statement within an "else" block. Use elseif instead.
// Remove file
if ( unlink( $file_path ) ) {
WP_CLI::log( "File removed: {$file}" );
++$count;
} else {
WP_CLI::debug( "Failed to remove file: {$file}", 'core' );
}
}
}
Comment thread
swissspidy marked this conversation as resolved.
Outdated
}

if ( $count ) {
WP_CLI::log( number_format( $count ) . ' files cleaned up.' );
} else {
WP_CLI::log( 'No files found that need cleaning up.' );
Comment thread
swissspidy marked this conversation as resolved.
Outdated
}
}

/**
* Clean up old files from $_old_files that are not tracked in checksums.
*
* This method is used as a supplement when checksums ARE available.
* It unconditionally deletes files from $_old_files that are not present in either
* the old or new checksums, as these files cannot be verified for modifications.
*
* @param array $old_checksums Old checksums array.
* @param array $new_checksums New checksums array.
Comment thread
swissspidy marked this conversation as resolved.
Outdated
*/
private function cleanup_old_files_not_in_checksums( $old_checksums, $new_checksums ) {
$old_files = $this->get_old_files_list();
if ( empty( $old_files ) ) {
return;
}

// Combine all files from both checksum arrays
$all_checksum_files = array_merge( array_keys( $old_checksums ), array_keys( $new_checksums ) );
$all_checksum_files = array_unique( $all_checksum_files );

// Find files in $_old_files that are not in checksums
$files_to_remove = array_diff( $old_files, $all_checksum_files );

if ( empty( $files_to_remove ) ) {
return;
}

$count = 0;
foreach ( $files_to_remove as $file ) {
// wp-content should be considered user data
if ( 0 === stripos( $file, 'wp-content' ) ) {
continue;
}

$file_path = ABSPATH . $file;

// Handle both files and directories
if ( file_exists( $file_path ) ) {
if ( is_dir( $file_path ) ) {
// Remove directory recursively
if ( $this->remove_directory( $file_path ) ) {
WP_CLI::log( "Directory removed: {$file}" );
++$count;
} else {
WP_CLI::debug( "Failed to remove directory: {$file}", 'core' );
}
} else {

Check failure on line 1719 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

If control structure block found as the only statement within an "else" block. Use elseif instead.
// Remove file
if ( unlink( $file_path ) ) {
WP_CLI::log( "File removed: {$file}" );
++$count;
} else {
WP_CLI::debug( "Failed to remove file: {$file}", 'core' );
}
}
}
}

if ( $count ) {
WP_CLI::log( number_format( $count ) . ' additional old files cleaned up.' );
}
}

/**
* Get the list of old files from WordPress core.
*
* @return array Array of old file paths, or empty array if not available.
*/
private function get_old_files_list() {
// Include WordPress core's update file to access the $_old_files list
if ( ! file_exists( ABSPATH . 'wp-admin/includes/update-core.php' ) ) {
WP_CLI::warning( 'Could not find update-core.php. Please cleanup files manually.' );
return array();
}

require_once ABSPATH . 'wp-admin/includes/update-core.php';

global $_old_files;

if ( empty( $_old_files ) || ! is_array( $_old_files ) ) {
return array();
}

return $_old_files;
}

/**
* Recursively remove a directory and its contents.
*
* @param string $dir Directory path to remove.
* @return bool True on success, false on failure.
*/
private function remove_directory( $dir ) {
Comment thread
swissspidy marked this conversation as resolved.
Outdated
if ( ! is_dir( $dir ) ) {
return false;
}

$items = scandir( $dir );
if ( false === $items ) {
WP_CLI::debug( "Failed to scan directory: {$dir}", 'core' );
return false;
}

foreach ( $items as $item ) {
if ( '.' === $item || '..' === $item ) {
continue;
}

$path = $dir . '/' . $item;
Comment thread
swissspidy marked this conversation as resolved.
Outdated

if ( is_dir( $path ) ) {
if ( ! $this->remove_directory( $path ) ) {
WP_CLI::debug( "Failed to remove subdirectory: {$path}", 'core' );
return false;
}
Comment thread
swissspidy marked this conversation as resolved.
Outdated
} else {

Check failure on line 1788 in src/Core_Command.php

View workflow job for this annotation

GitHub Actions / code-quality / PHPCS

If control structure block found as the only statement within an "else" block. Use elseif instead.
if ( ! unlink( $path ) ) {
WP_CLI::debug( "Failed to remove file in directory: {$path}", 'core' );
return false;
Comment thread
swissspidy marked this conversation as resolved.
Outdated
}
}
}

if ( ! rmdir( $dir ) ) {
WP_CLI::debug( "Failed to remove directory: {$dir}", 'core' );
return false;
}

return true;
}

private static function strip_content_dir( $zip_file ) {
Expand Down
Loading