Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions features/core-check-update.feature
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,19 @@ Feature: Check for more recent versions
"""
---
"""

Scenario: Check update shows warning when version check API fails
Given a WP install
And that HTTP requests to https://api.wordpress.org/core/version-check/1.7/ will respond with:
"""
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain

<Error body>
"""

When I try `wp core check-update --force-check`
Then STDERR should contain:
"""
Warning: Failed to check for updates
"""
110 changes: 109 additions & 1 deletion src/Core_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
*/
class Core_Command extends WP_CLI_Command {

/**
* Stores HTTP API errors encountered during version check.
*
* @var \WP_Error|null
*/
private $version_check_error = null;

/**
* Checks for WordPress updates via Version Check API.
*
Expand Down Expand Up @@ -92,6 +99,14 @@ public function check_update( $args, $assoc_args ) {
[ 'version', 'update_type', 'package_url' ]
);
$formatter->display_items( $updates );
} elseif ( $this->version_check_error ) {
// If there was an HTTP error during version check, show a warning
WP_CLI::warning(
sprintf(
'Failed to check for updates: %s',
$this->version_check_error->get_error_message()
)
);
} else {
WP_CLI::success( 'WordPress is at the latest version.' );
}
Expand Down Expand Up @@ -1453,7 +1468,25 @@ private function get_download_url( $version, $locale = 'en_US', $file_type = 'zi
*/
private function get_updates( $assoc_args ) {
$force_check = Utils\get_flag_value( $assoc_args, 'force-check' );
wp_version_check( [], $force_check );

// Reset error tracking
$this->version_check_error = null;

// Hook into pre_http_request with max priority to capture errors during version check
// This is necessary because tests and plugins may use pre_http_request to mock responses
add_filter( 'pre_http_request', [ $this, 'capture_version_check_error' ], PHP_INT_MAX, 3 );

// Also hook into http_api_debug to capture errors from real HTTP requests
// This fires when pre_http_request doesn't short-circuit the request
add_action( 'http_api_debug', [ $this, 'capture_version_check_error_from_response' ], 10, 5 );

try {
wp_version_check( [], $force_check );
} finally {
// Ensure the hooks are always removed, even if wp_version_check() throws an exception
remove_filter( 'pre_http_request', [ $this, 'capture_version_check_error' ], PHP_INT_MAX );
remove_action( 'http_api_debug', [ $this, 'capture_version_check_error_from_response' ], 10 );
}

/**
* @var object{updates: array<object{version: string, locale: string, packages: object{partial?: string, full: string}}>}|false $from_api
Expand Down Expand Up @@ -1510,6 +1543,81 @@ private function get_updates( $assoc_args ) {
return array_values( $updates );
}

/**
* Sets or clears the version check error property based on an HTTP response.
*
* @param mixed $response The HTTP response (WP_Error, array, or other).
*/
private function set_version_check_error( $response ) {
if ( is_wp_error( $response ) ) {
$this->version_check_error = $response;
} elseif ( is_array( $response ) && isset( $response['response']['code'] ) && $response['response']['code'] >= 400 ) {
// HTTP error status code (4xx or 5xx) - convert to WP_Error for consistency
$this->version_check_error = new \WP_Error(
'http_request_failed',
sprintf(
'HTTP request failed with status %d: %s',
$response['response']['code'],
isset( $response['response']['message'] ) ? $response['response']['message'] : 'Unknown error'
)
);
} else {
// Clear any previous error if we got a successful response.
$this->version_check_error = null;
}
}

/**
* Handles the pre_http_request filter to capture HTTP errors during version check.
*
* This method signature matches the pre_http_request filter signature.
* Uses PHP_INT_MAX priority to run after test mocking and plugin modifications.
*
* @param false|array|WP_Error $response A preemptive return value of an HTTP request.
* @param array $args HTTP request arguments.
* @param string $url The request URL.
* @return false|array|WP_Error The response, unmodified.
*/
public function capture_version_check_error( $response, $args, $url ) {
if ( false === strpos( $url, 'api.wordpress.org/core/version-check' ) ) {
return $response;
}

// A `false` response means the request is not being preempted.
// In this case, we don't want to change the error status, as the subsequent
// `http_api_debug` hook will handle the actual response.
if ( false !== $response ) {
$this->set_version_check_error( $response );
}

return $response;
}

/**
* Handles the http_api_debug action to capture HTTP errors from real requests.
*
* This fires when pre_http_request doesn't short-circuit the request.
* Uses the http_api_debug action hook signature.
*
* @param array|WP_Error $response HTTP response or WP_Error object.
* @param string $context Context of the HTTP request.
* @param string $_class HTTP transport class name (unused).
* @param array $_args HTTP request arguments (unused).
* @param string $url URL being requested.
*/
public function capture_version_check_error_from_response( $response, $context, $_class, $_args, $url ) {
if ( false === strpos( $url, 'api.wordpress.org/core/version-check' ) ) {
return;
}

// Only capture on response, not pre_response
if ( 'response' !== $context ) {
return;
}

$this->set_version_check_error( $response );
}

/**
* Clean up extra files.
*
Expand Down