Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 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
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.
*
* @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 ) {
// When checksums are not available, use WordPress core's $_old_files list
$this->cleanup_old_files();
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.
*/
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' );
}
}
}
}

if ( $count ) {
WP_CLI::log( number_format( $count ) . ' files cleaned up.' );
} else {
WP_CLI::log( 'No files found that need cleaning up.' );
}
}

/**
* 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.
*/
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 ) {
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;

if ( is_dir( $path ) ) {
if ( ! $this->remove_directory( $path ) ) {
WP_CLI::debug( "Failed to remove subdirectory: {$path}", 'core' );
return false;
}
} 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;
}
}
}

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