Skip to content

Commit 463a2d7

Browse files
authored
Merge branch 'main' into copilot/add-delete-old-core-files
2 parents 0366f61 + c8fd1ed commit 463a2d7

File tree

9 files changed

+247
-12
lines changed

9 files changed

+247
-12
lines changed

.github/workflows/code-quality.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
branches:
77
- main
88
- master
9+
schedule:
10+
- cron: '17 2 * * *' # Run every day on a seemly random time.
911

1012
jobs:
1113
code-quality:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717

1818
steps:
1919
- name: Checkout code
20-
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
20+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2121

2222
- name: Check existence of composer.json file
2323
id: check_composer_file

.typos.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[default]
2+
extend-ignore-re = [
3+
"(?Rm)^.*(#|//)\\s*spellchecker:disable-line$",
4+
"(?s)(#|//)\\s*spellchecker:off.*?\\n\\s*(#|//)\\s*spellchecker:on",
5+
"(#|//)\\s*spellchecker:ignore-next-line\\n.*"
6+
]

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ Transforms an existing single-site installation into a multisite installation.
239239
wp core multisite-convert [--title=<network-title>] [--base=<url-path>] [--subdomains] [--skip-config]
240240
~~~
241241

242+
**Alias:** `install-network`
243+
242244
Creates the multisite database tables, and adds the multisite constants
243245
to wp-config.php.
244246

@@ -344,6 +346,8 @@ Updates WordPress to a newer version.
344346
wp core update [<zip>] [--minor] [--version=<version>] [--force] [--locale=<locale>] [--format=<format>] [--insecure]
345347
~~~
346348

349+
**Alias:** `upgrade`
350+
347351
Defaults to updating WordPress to the latest version.
348352

349353
If you see "Error: Another update is currently in progress.", you may

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"require": {
1515
"composer/semver": "^1.4 || ^2 || ^3",
16-
"wp-cli/wp-cli": "^2.12"
16+
"wp-cli/wp-cli": "^2.13"
1717
},
1818
"require-dev": {
1919
"wp-cli/checksum-command": "^1 || ^2",

features/core-check-update.feature

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,19 @@ Feature: Check for more recent versions
7070
"""
7171
---
7272
"""
73+
74+
Scenario: Check update shows warning when version check API fails
75+
Given a WP install
76+
And that HTTP requests to https://api.wordpress.org/core/version-check/1.7/ will respond with:
77+
"""
78+
HTTP/1.1 500 Internal Server Error
79+
Content-Type: text/plain
80+
81+
<Error body>
82+
"""
83+
84+
When I try `wp core check-update --force-check`
85+
Then STDERR should contain:
86+
"""
87+
Warning: Failed to check for updates
88+
"""

features/core-update.feature

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ Feature: Update WordPress core
167167
When I run `wp core update`
168168
Then STDOUT should contain:
169169
"""
170-
WordPress is up to date
170+
WordPress is up to date at version
171171
"""
172172
And STDOUT should not contain:
173173
"""
@@ -504,3 +504,57 @@ Feature: Update WordPress core
504504
# Verify files from $_old_files were removed
505505
And the wp-includes/blocks/post-author/editor.css file should not exist
506506
And the wp-includes/blocks/post-author/editor.min.css file should not exist
507+
508+
@require-php-7.0 @require-wp-6.1
509+
Scenario: Attempting to downgrade without --force shows helpful message
510+
Given a WP install
511+
512+
When I run `wp core version`
513+
Then save STDOUT as {WP_CURRENT_VERSION}
514+
515+
When I try `wp core update --version=6.0`
516+
Then STDOUT should contain:
517+
"""
518+
WordPress is up to date at version
519+
"""
520+
And STDOUT should contain:
521+
"""
522+
is older than the current version
523+
"""
524+
And STDOUT should contain:
525+
"""
526+
Use --force to update anyway
527+
"""
528+
And STDOUT should not contain:
529+
"""
530+
Success:
531+
"""
532+
And the return code should be 0
533+
534+
When I run `wp core update --version=6.0 --force`
535+
Then STDOUT should contain:
536+
"""
537+
Updating to version 6.0
538+
"""
539+
And STDOUT should contain:
540+
"""
541+
Success: WordPress updated successfully.
542+
"""
543+
544+
Scenario: Show helpful tip when update is locked
545+
Given a WP install
546+
547+
When I run `wp option update core_updater.lock 100000000000000`
548+
And I try `wp core update --version=trunk`
549+
Then STDERR should contain:
550+
"""
551+
Another update is currently in progress. You may need to run `wp option delete core_updater.lock` after verifying another update isn't actually running.
552+
"""
553+
And the return code should be 1
554+
555+
# Clean up the lock
556+
When I run `wp option delete core_updater.lock`
557+
Then STDOUT should contain:
558+
"""
559+
Success:
560+
"""

src/Core_Command.php

Lines changed: 156 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,18 @@
2828
* 4.5.2
2929
*
3030
* @package wp-cli
31+
*
32+
* @phpstan-type HTTP_Response array{headers: array<string, string>, body: string, response: array{code:false|int, message: false|string}, cookies: array<string, string>, http_response: mixed}
3133
*/
3234
class Core_Command extends WP_CLI_Command {
3335

36+
/**
37+
* Stores HTTP API errors encountered during version check.
38+
*
39+
* @var \WP_Error|null
40+
*/
41+
private $version_check_error = null;
42+
3443
/**
3544
* Checks for WordPress updates via Version Check API.
3645
*
@@ -92,6 +101,14 @@ public function check_update( $args, $assoc_args ) {
92101
[ 'version', 'update_type', 'package_url' ]
93102
);
94103
$formatter->display_items( $updates );
104+
} elseif ( $this->version_check_error ) {
105+
// If there was an HTTP error during version check, show a warning
106+
WP_CLI::warning(
107+
sprintf(
108+
'Failed to check for updates: %s',
109+
$this->version_check_error->get_error_message()
110+
)
111+
);
95112
} else {
96113
WP_CLI::success( 'WordPress is at the latest version.' );
97114
}
@@ -644,6 +661,10 @@ private static function set_multisite_defaults( $assoc_args ) {
644661
}
645662

646663
private function do_install( $assoc_args ) {
664+
/**
665+
* @var \wpdb $wpdb
666+
*/
667+
global $wpdb;
647668
if ( is_blog_installed() ) {
648669
return false;
649670
}
@@ -695,7 +716,7 @@ function wp_new_blog_notification() {
695716
$args['locale']
696717
);
697718

698-
if ( ! empty( $GLOBALS['wpdb']->last_error ) ) {
719+
if ( ! empty( $wpdb->last_error ) ) {
699720
WP_CLI::error( 'Installation produced database errors, and may have partially or completely failed.' );
700721
}
701722

@@ -1243,7 +1264,12 @@ static function () {
12431264
if ( is_wp_error( $result ) ) {
12441265
$message = WP_CLI::error_to_string( $result );
12451266
if ( 'up_to_date' !== $result->get_error_code() ) {
1246-
WP_CLI::error( $message );
1267+
// Check if the error is related to the core_updater.lock
1268+
if ( self::is_lock_error( $result ) ) {
1269+
WP_CLI::error( rtrim( $message, '.' ) . '. You may need to run `wp option delete core_updater.lock` after verifying another update isn\'t actually running.' );
1270+
} else {
1271+
WP_CLI::error( $message );
1272+
}
12471273
} else {
12481274
WP_CLI::success( $message );
12491275
}
@@ -1278,8 +1304,14 @@ static function () {
12781304

12791305
WP_CLI::success( 'WordPress updated successfully.' );
12801306
}
1307+
// Check if user attempted to downgrade without --force
1308+
} elseif ( ! empty( Utils\get_flag_value( $assoc_args, 'version' ) ) && version_compare( Utils\get_flag_value( $assoc_args, 'version' ), $wp_version, '<' ) ) {
1309+
// Requested version is older than current (downgrade attempt)
1310+
WP_CLI::log( "WordPress is up to date at version {$wp_version}." );
1311+
WP_CLI::log( 'The version you requested (' . Utils\get_flag_value( $assoc_args, 'version' ) . ") is older than the current version ({$wp_version})." );
1312+
WP_CLI::log( 'Use --force to update anyway (e.g., to downgrade to version ' . Utils\get_flag_value( $assoc_args, 'version' ) . ').' );
12811313
} else {
1282-
WP_CLI::success( 'WordPress is up to date.' );
1314+
WP_CLI::success( "WordPress is up to date at version {$wp_version}." );
12831315
}
12841316
}
12851317

@@ -1448,7 +1480,25 @@ private function get_download_url( $version, $locale = 'en_US', $file_type = 'zi
14481480
*/
14491481
private function get_updates( $assoc_args ) {
14501482
$force_check = Utils\get_flag_value( $assoc_args, 'force-check' );
1451-
wp_version_check( [], $force_check );
1483+
1484+
// Reset error tracking
1485+
$this->version_check_error = null;
1486+
1487+
// Hook into pre_http_request with max priority to capture errors during version check
1488+
// This is necessary because tests and plugins may use pre_http_request to mock responses
1489+
add_filter( 'pre_http_request', [ $this, 'capture_version_check_error' ], PHP_INT_MAX, 3 );
1490+
1491+
// Also hook into http_api_debug to capture errors from real HTTP requests
1492+
// This fires when pre_http_request doesn't short-circuit the request
1493+
add_action( 'http_api_debug', [ $this, 'capture_version_check_error_from_response' ], 10, 5 );
1494+
1495+
try {
1496+
wp_version_check( [], $force_check );
1497+
} finally {
1498+
// Ensure the hooks are always removed, even if wp_version_check() throws an exception
1499+
remove_filter( 'pre_http_request', [ $this, 'capture_version_check_error' ], PHP_INT_MAX );
1500+
remove_action( 'http_api_debug', [ $this, 'capture_version_check_error_from_response' ], 10 );
1501+
}
14521502

14531503
/**
14541504
* @var object{updates: array<object{version: string, locale: string, packages: object{partial?: string, full: string}}>}|false $from_api
@@ -1458,6 +1508,10 @@ private function get_updates( $assoc_args ) {
14581508
return [];
14591509
}
14601510

1511+
/**
1512+
* @var array{wp_version: string} $GLOBALS
1513+
*/
1514+
14611515
$compare_version = str_replace( '-src', '', $GLOBALS['wp_version'] );
14621516

14631517
$updates = [
@@ -1505,6 +1559,87 @@ private function get_updates( $assoc_args ) {
15051559
return array_values( $updates );
15061560
}
15071561

1562+
/**
1563+
* Sets or clears the version check error property based on an HTTP response.
1564+
*
1565+
* @param mixed|\WP_Error $response The HTTP response (WP_Error, array, or other).
1566+
*
1567+
* @phpstan-param HTTP_Response|WP_Error $response
1568+
*/
1569+
private function set_version_check_error( $response ) {
1570+
if ( is_wp_error( $response ) ) {
1571+
$this->version_check_error = $response;
1572+
} elseif ( is_array( $response ) && isset( $response['response']['code'] ) && $response['response']['code'] >= 400 ) {
1573+
// HTTP error status code (4xx or 5xx) - convert to WP_Error for consistency
1574+
$this->version_check_error = new \WP_Error(
1575+
'http_request_failed',
1576+
sprintf(
1577+
'HTTP request failed with status %d: %s',
1578+
$response['response']['code'],
1579+
isset( $response['response']['message'] ) ? $response['response']['message'] : 'Unknown error'
1580+
)
1581+
);
1582+
} else {
1583+
// Clear any previous error if we got a successful response.
1584+
$this->version_check_error = null;
1585+
}
1586+
}
1587+
1588+
/**
1589+
* Handles the pre_http_request filter to capture HTTP errors during version check.
1590+
*
1591+
* This method signature matches the pre_http_request filter signature.
1592+
* Uses PHP_INT_MAX priority to run after test mocking and plugin modifications.
1593+
*
1594+
* @param false|array|WP_Error $response A preemptive return value of an HTTP request.
1595+
* @param array $args HTTP request arguments.
1596+
* @param string $url The request URL.
1597+
* @return false|array|WP_Error The response, unmodified.
1598+
*
1599+
* @phpstan-param HTTP_Response|WP_Error|false $response
1600+
*/
1601+
public function capture_version_check_error( $response, $args, $url ) {
1602+
if ( false === strpos( $url, 'api.wordpress.org/core/version-check' ) ) {
1603+
return $response;
1604+
}
1605+
1606+
// A `false` response means the request is not being preempted.
1607+
// In this case, we don't want to change the error status, as the subsequent
1608+
// `http_api_debug` hook will handle the actual response.
1609+
if ( false !== $response ) {
1610+
$this->set_version_check_error( $response );
1611+
}
1612+
1613+
return $response;
1614+
}
1615+
1616+
/**
1617+
* Handles the http_api_debug action to capture HTTP errors from real requests.
1618+
*
1619+
* This fires when pre_http_request doesn't short-circuit the request.
1620+
* Uses the http_api_debug action hook signature.
1621+
*
1622+
* @param array|WP_Error $response HTTP response or WP_Error object.
1623+
* @param string $context Context of the HTTP request.
1624+
* @param string $_class HTTP transport class name (unused).
1625+
* @param array $_args HTTP request arguments (unused).
1626+
* @param string $url URL being requested.
1627+
*
1628+
* @phpstan-param HTTP_Response|WP_Error $response
1629+
*/
1630+
public function capture_version_check_error_from_response( $response, $context, $_class, $_args, $url ) {
1631+
if ( false === strpos( $url, 'api.wordpress.org/core/version-check' ) ) {
1632+
return;
1633+
}
1634+
1635+
// Only capture on response, not pre_response
1636+
if ( 'response' !== $context ) {
1637+
return;
1638+
}
1639+
1640+
$this->set_version_check_error( $response );
1641+
}
1642+
15081643
/**
15091644
* Clean up extra files.
15101645
*
@@ -1904,4 +2039,21 @@ function () use ( $new_zip_file ) {
19042039
WP_CLI::error( 'ZipArchive failed to open ZIP file.' );
19052040
}
19062041
}
2042+
2043+
/**
2044+
* Checks if a WP_Error is related to the core_updater.lock.
2045+
*
2046+
* @param \WP_Error $error The error object to check.
2047+
* @return bool True if the error is related to the lock, false otherwise.
2048+
*/
2049+
private static function is_lock_error( $error ) {
2050+
// Check for the 'locked' error code used by WordPress Core
2051+
if ( 'locked' === $error->get_error_code() ) {
2052+
return true;
2053+
}
2054+
2055+
// Also check if the error message contains the lock text as a fallback
2056+
$message = WP_CLI::error_to_string( $error );
2057+
return false !== stripos( $message, 'another update is currently in progress' );
2058+
}
19072059
}

src/WP_CLI/Core/CoreUpgrader.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,8 @@ public function download_package( $package, $check_signatures = false, $hook_ext
6767
$hook_extra
6868
);
6969

70-
/**
71-
* @var false|string|\WP_Error $reply
72-
*/
73-
7470
if ( false !== $reply ) {
71+
// @phpstan-ignore return.type
7572
return $reply;
7673
}
7774

@@ -96,7 +93,11 @@ function () use ( $temp ) {
9693
}
9794
);
9895

99-
$cache = WP_CLI::get_cache();
96+
$cache = WP_CLI::get_cache();
97+
98+
/**
99+
* @var object{locale: string} $update
100+
*/
100101
$update = $GLOBALS['wpcli_core_update_obj'];
101102
$cache_key = "core/{$filename}-{$update->locale}.{$extension}";
102103

0 commit comments

Comments
 (0)