Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
"""
113 changes: 112 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,84 @@ private function get_updates( $assoc_args ) {
return array_values( $updates );
}

/**
* 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;
}

// Store the error if the response is a WP_Error
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'
)
);
} elseif ( false !== $response ) {
// Clear any previous error if we got a successful mocked response
$this->version_check_error = null;
}

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;
}

// Store the error if the response is a WP_Error
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;
}
}

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