|
31 | 31 | */ |
32 | 32 | class Core_Command extends WP_CLI_Command { |
33 | 33 |
|
| 34 | + /** |
| 35 | + * Stores HTTP API errors encountered during version check. |
| 36 | + * |
| 37 | + * @var \WP_Error|null |
| 38 | + */ |
| 39 | + private $version_check_error = null; |
| 40 | + |
34 | 41 | /** |
35 | 42 | * Checks for WordPress updates via Version Check API. |
36 | 43 | * |
@@ -92,6 +99,14 @@ public function check_update( $args, $assoc_args ) { |
92 | 99 | [ 'version', 'update_type', 'package_url' ] |
93 | 100 | ); |
94 | 101 | $formatter->display_items( $updates ); |
| 102 | + } elseif ( $this->version_check_error ) { |
| 103 | + // If there was an HTTP error during version check, show a warning |
| 104 | + WP_CLI::warning( |
| 105 | + sprintf( |
| 106 | + 'Failed to check for updates: %s', |
| 107 | + $this->version_check_error->get_error_message() |
| 108 | + ) |
| 109 | + ); |
95 | 110 | } else { |
96 | 111 | WP_CLI::success( 'WordPress is at the latest version.' ); |
97 | 112 | } |
@@ -1453,7 +1468,25 @@ private function get_download_url( $version, $locale = 'en_US', $file_type = 'zi |
1453 | 1468 | */ |
1454 | 1469 | private function get_updates( $assoc_args ) { |
1455 | 1470 | $force_check = Utils\get_flag_value( $assoc_args, 'force-check' ); |
1456 | | - wp_version_check( [], $force_check ); |
| 1471 | + |
| 1472 | + // Reset error tracking |
| 1473 | + $this->version_check_error = null; |
| 1474 | + |
| 1475 | + // Hook into pre_http_request with max priority to capture errors during version check |
| 1476 | + // This is necessary because tests and plugins may use pre_http_request to mock responses |
| 1477 | + add_filter( 'pre_http_request', [ $this, 'capture_version_check_error' ], PHP_INT_MAX, 3 ); |
| 1478 | + |
| 1479 | + // Also hook into http_api_debug to capture errors from real HTTP requests |
| 1480 | + // This fires when pre_http_request doesn't short-circuit the request |
| 1481 | + add_action( 'http_api_debug', [ $this, 'capture_version_check_error_from_response' ], 10, 5 ); |
| 1482 | + |
| 1483 | + try { |
| 1484 | + wp_version_check( [], $force_check ); |
| 1485 | + } finally { |
| 1486 | + // Ensure the hooks are always removed, even if wp_version_check() throws an exception |
| 1487 | + remove_filter( 'pre_http_request', [ $this, 'capture_version_check_error' ], PHP_INT_MAX ); |
| 1488 | + remove_action( 'http_api_debug', [ $this, 'capture_version_check_error_from_response' ], 10 ); |
| 1489 | + } |
1457 | 1490 |
|
1458 | 1491 | /** |
1459 | 1492 | * @var object{updates: array<object{version: string, locale: string, packages: object{partial?: string, full: string}}>}|false $from_api |
@@ -1510,6 +1543,81 @@ private function get_updates( $assoc_args ) { |
1510 | 1543 | return array_values( $updates ); |
1511 | 1544 | } |
1512 | 1545 |
|
| 1546 | + /** |
| 1547 | + * Sets or clears the version check error property based on an HTTP response. |
| 1548 | + * |
| 1549 | + * @param mixed $response The HTTP response (WP_Error, array, or other). |
| 1550 | + */ |
| 1551 | + private function set_version_check_error( $response ) { |
| 1552 | + if ( is_wp_error( $response ) ) { |
| 1553 | + $this->version_check_error = $response; |
| 1554 | + } elseif ( is_array( $response ) && isset( $response['response']['code'] ) && $response['response']['code'] >= 400 ) { |
| 1555 | + // HTTP error status code (4xx or 5xx) - convert to WP_Error for consistency |
| 1556 | + $this->version_check_error = new \WP_Error( |
| 1557 | + 'http_request_failed', |
| 1558 | + sprintf( |
| 1559 | + 'HTTP request failed with status %d: %s', |
| 1560 | + $response['response']['code'], |
| 1561 | + isset( $response['response']['message'] ) ? $response['response']['message'] : 'Unknown error' |
| 1562 | + ) |
| 1563 | + ); |
| 1564 | + } else { |
| 1565 | + // Clear any previous error if we got a successful response. |
| 1566 | + $this->version_check_error = null; |
| 1567 | + } |
| 1568 | + } |
| 1569 | + |
| 1570 | + /** |
| 1571 | + * Handles the pre_http_request filter to capture HTTP errors during version check. |
| 1572 | + * |
| 1573 | + * This method signature matches the pre_http_request filter signature. |
| 1574 | + * Uses PHP_INT_MAX priority to run after test mocking and plugin modifications. |
| 1575 | + * |
| 1576 | + * @param false|array|WP_Error $response A preemptive return value of an HTTP request. |
| 1577 | + * @param array $args HTTP request arguments. |
| 1578 | + * @param string $url The request URL. |
| 1579 | + * @return false|array|WP_Error The response, unmodified. |
| 1580 | + */ |
| 1581 | + public function capture_version_check_error( $response, $args, $url ) { |
| 1582 | + if ( false === strpos( $url, 'api.wordpress.org/core/version-check' ) ) { |
| 1583 | + return $response; |
| 1584 | + } |
| 1585 | + |
| 1586 | + // A `false` response means the request is not being preempted. |
| 1587 | + // In this case, we don't want to change the error status, as the subsequent |
| 1588 | + // `http_api_debug` hook will handle the actual response. |
| 1589 | + if ( false !== $response ) { |
| 1590 | + $this->set_version_check_error( $response ); |
| 1591 | + } |
| 1592 | + |
| 1593 | + return $response; |
| 1594 | + } |
| 1595 | + |
| 1596 | + /** |
| 1597 | + * Handles the http_api_debug action to capture HTTP errors from real requests. |
| 1598 | + * |
| 1599 | + * This fires when pre_http_request doesn't short-circuit the request. |
| 1600 | + * Uses the http_api_debug action hook signature. |
| 1601 | + * |
| 1602 | + * @param array|WP_Error $response HTTP response or WP_Error object. |
| 1603 | + * @param string $context Context of the HTTP request. |
| 1604 | + * @param string $_class HTTP transport class name (unused). |
| 1605 | + * @param array $_args HTTP request arguments (unused). |
| 1606 | + * @param string $url URL being requested. |
| 1607 | + */ |
| 1608 | + public function capture_version_check_error_from_response( $response, $context, $_class, $_args, $url ) { |
| 1609 | + if ( false === strpos( $url, 'api.wordpress.org/core/version-check' ) ) { |
| 1610 | + return; |
| 1611 | + } |
| 1612 | + |
| 1613 | + // Only capture on response, not pre_response |
| 1614 | + if ( 'response' !== $context ) { |
| 1615 | + return; |
| 1616 | + } |
| 1617 | + |
| 1618 | + $this->set_version_check_error( $response ); |
| 1619 | + } |
| 1620 | + |
1513 | 1621 | /** |
1514 | 1622 | * Clean up extra files. |
1515 | 1623 | * |
|
0 commit comments