Skip to content
110 changes: 110 additions & 0 deletions features/http-logging.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
Feature: HTTP request logging

Scenario: HTTP requests are logged when WordPress isn't loaded
Given an empty directory

When I try `wp cli check-update --debug=http`
Then STDERR should contain:
"""
Debug: HTTP GET request to https://api.github.com
"""
And the return code should be 0

Scenario: HTTP requests are logged with --debug=http flag
Given a WP installation
And a http-test.php file:
"""
<?php
WP_CLI::add_command( 'http-test', function() {
// Make a test HTTP request using WP-CLI's http_request
try {
WP_CLI\Utils\http_request( 'GET', 'https://api.wordpress.org/core/version-check/1.7/', null, [], [ 'timeout' => 5 ] );
WP_CLI::success( 'HTTP request completed' );
} catch ( Exception $e ) {
WP_CLI::error( 'HTTP request failed: ' . $e->getMessage() );
}
});
"""
And a wp-cli.yml file:
"""
require:
- http-test.php
"""

When I try `wp http-test --debug=http`
Then STDERR should contain:
"""
Debug: HTTP GET request to https://api.wordpress.org/core/version-check/1.7/
"""
And the return code should be 0

Scenario: HTTP requests are not logged without debug flag
Given a WP installation
And a http-test.php file:
"""
<?php
WP_CLI::add_command( 'http-test', function() {
// Make a test HTTP request
try {
WP_CLI\Utils\http_request( 'GET', 'https://api.wordpress.org/core/version-check/1.7/', null, [], [ 'timeout' => 5 ] );
WP_CLI::success( 'HTTP request completed' );
} catch ( Exception $e ) {
WP_CLI::error( 'HTTP request failed: ' . $e->getMessage() );
}
});
"""
And a wp-cli.yml file:
"""
require:
- http-test.php
"""

When I run `wp http-test`
Then STDERR should not contain:
"""
HTTP GET request to
"""
And the return code should be 0

Scenario: Different HTTP methods are logged correctly
Given a WP installation
And a http-methods-test.php file:
"""
<?php
WP_CLI::add_command( 'http-methods-test', function() {
// Test different HTTP methods
$test_url = 'https://httpbin.org/';

// GET request
try {
WP_CLI\Utils\http_request( 'GET', $test_url . 'get', null, [], [ 'timeout' => 5 ] );
} catch ( Exception $e ) {
// Ignore errors for this test
}

// POST request
try {
WP_CLI\Utils\http_request( 'POST', $test_url . 'post', ['test' => 'data'], [], [ 'timeout' => 5 ] );
} catch ( Exception $e ) {
// Ignore errors for this test
}

WP_CLI::success( 'Test completed' );
});
"""
And a wp-cli.yml file:
"""
require:
- http-methods-test.php
"""

When I try `wp http-methods-test --debug=http`
Then STDERR should contain:
"""
Debug: HTTP GET request to https://httpbin.org/get
"""
And STDERR should contain:
"""
Debug: HTTP POST request to https://httpbin.org/post
"""
And the return code should be 0
21 changes: 21 additions & 0 deletions php/WP_CLI/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,15 @@ public function start() {
return;
}

// Log WP-CLI HTTP requests
WP_CLI::add_hook(
'http_request_options',
static function ( $options, $method, $url ) {
WP_CLI::debug( sprintf( 'HTTP %s request to %s', $method, $url ), 'http' );
return $options;
}
);

// Handle --path parameter
self::set_wp_root( $this->find_wp_root() );

Expand Down Expand Up @@ -1554,6 +1563,18 @@ private function setup_bootstrap_hooks(): void {
WP_CLI::add_wp_hook( 'setup_theme', [ $this, 'action_setup_theme_wp_cli_skip_themes' ], 999 );
}

// Log WordPress HTTP API requests
WP_CLI::add_wp_hook(
'pre_http_request',
static function ( $response, $args, $url ) {
$method = isset( $args['method'] ) ? $args['method'] : 'GET';
WP_CLI::debug( sprintf( 'HTTP %s request to %s', $method, $url ), 'http' );
return $response;
},
10,
3
);

if ( $this->cmd_starts_with( [ 'help' ] ) ) {
// Try to trap errors on help.
$help_handler = [ $this, 'help_wp_die_handler' ]; // Avoid any cross PHP version issues by not using $this in anon function.
Expand Down
2 changes: 1 addition & 1 deletion php/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ function http_request( $method, $url, $data = null, $headers = [], $options = []
/**
* @var array{halt_on_error?: bool, verify: bool|string, insecure?: bool} $options
*/
$options = WP_CLI::do_hook( 'http_request_options', $options );
$options = WP_CLI::do_hook( 'http_request_options', $options, $method, $url, $data, $headers );

RequestsLibrary::register_autoloader();

Expand Down
58 changes: 58 additions & 0 deletions tests/HttpRequestLoggingTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

use WP_CLI\Tests\TestCase;
use WP_CLI\Utils;

class HttpRequestLoggingTest extends TestCase {

public static function set_up_before_class() {
require_once dirname( __DIR__ ) . '/php/class-wp-cli.php';
require_once __DIR__ . '/mock-requests-transport.php';
}

public function testHttpRequestOptionsHookReceivesAllParameters(): void {
$hook_called = false;
$received_method = null;
$received_url = null;
$received_data = null;
$received_headers = null;

WP_CLI::add_hook(
'http_request_options',
function ( $options, $method, $url, $data, $headers ) use ( &$hook_called, &$received_method, &$received_url, &$received_data, &$received_headers ) {
$hook_called = true;
$received_method = $method;
$received_url = $url;
$received_data = $data;
$received_headers = $headers;
return $options;
}
);

$test_url = 'https://example.com/test';
$test_data = [ 'key' => 'value' ];
$test_headers = [ 'X-Test' => 'test' ];

try {
Utils\http_request(
'POST',
$test_url,
$test_data,
$test_headers,
[
'timeout' => 0.01,
'halt_on_error' => false,
]
);
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
} catch ( \RuntimeException $e ) {
// Expected to fail due to short timeout
}

$this->assertTrue( $hook_called, 'http_request_options hook should be called' );
$this->assertEquals( 'POST', $received_method, 'Method should be passed to hook' );
$this->assertEquals( $test_url, $received_url, 'URL should be passed to hook' );
$this->assertEquals( $test_data, $received_data, 'Data should be passed to hook' );
$this->assertEquals( $test_headers, $received_headers, 'Headers should be passed to hook' );
}
}