From d1d026bca71df34dbeb942495b6d737129386ebe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 11:01:06 +0000 Subject: [PATCH 1/3] Initial plan From de7744b2ccd43faa3f24bfa7c42adb2d0a7e417f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 11:06:28 +0000 Subject: [PATCH 2/3] Add hook for customizing HTTP REST auth args Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- inc/RestCommand.php | 75 +++++++++++++++++++++++------ tests/RestCommand_Test.php | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 tests/RestCommand_Test.php diff --git a/inc/RestCommand.php b/inc/RestCommand.php index cfb6b10..59a75fb 100644 --- a/inc/RestCommand.php +++ b/inc/RestCommand.php @@ -591,21 +591,9 @@ function ( $a, $b ) { } return array( $response->get_status(), $response->get_data(), $response->get_headers() ); } elseif ( 'http' === $this->scope ) { - $headers = array(); - if ( ! empty( $this->auth ) && 'basic' === $this->auth['type'] ) { - $username = isset( $this->auth['username'] ) ? $this->auth['username'] : ''; - $password = isset( $this->auth['password'] ) ? $this->auth['password'] : ''; - if ( is_scalar( $username ) && is_scalar( $password ) ) { - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode - $headers['Authorization'] = 'Basic ' . base64_encode( (string) $username . ':' . (string) $password ); - } - } - if ( 'OPTIONS' === $method ) { - $method = 'GET'; - $assoc_args['_method'] = 'OPTIONS'; - } + list( $method, $url, $assoc_args, $headers ) = $this->get_http_request_args( $method, $route, $assoc_args ); /** @var \WpOrg\Requests\Response $response */ - $response = Utils\http_request( $method, rtrim( $this->api_url, '/' ) . $route, $assoc_args, $headers ); + $response = Utils\http_request( $method, $url, $assoc_args, $headers ); $body = json_decode( $response->body, true ); if ( ! is_array( $body ) ) { $body = array(); @@ -633,6 +621,65 @@ function ( $a, $b ) { return array( 0, '', array() ); } + /** + * Build HTTP request arguments and allow custom authentication mechanisms. + * + * Hook: `restful_http_request_args` + * + * @param string $method + * @param string $route + * @param array $assoc_args + * + * @return array{0: string, 1: string, 2: array, 3: array} + */ + private function get_http_request_args( $method, $route, $assoc_args ) { + $headers = array(); + if ( ! empty( $this->auth ) && 'basic' === $this->auth['type'] ) { + $username = isset( $this->auth['username'] ) ? $this->auth['username'] : ''; + $password = isset( $this->auth['password'] ) ? $this->auth['password'] : ''; + if ( is_scalar( $username ) && is_scalar( $password ) ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + $headers['Authorization'] = 'Basic ' . base64_encode( (string) $username . ':' . (string) $password ); + } + } + + if ( 'OPTIONS' === $method ) { + $method = 'GET'; + $assoc_args['_method'] = 'OPTIONS'; + } + + $url = rtrim( $this->api_url, '/' ) . $route; + + /** @var array{method?: string, url?: string, assoc_args?: array, headers?: array} $request_args */ + $request_args = WP_CLI::do_hook( + 'restful_http_request_args', + array( + 'method' => $method, + 'url' => $url, + 'assoc_args' => $assoc_args, + 'headers' => $headers, + ), + $this + ); + + if ( is_array( $request_args ) ) { + if ( array_key_exists( 'method', $request_args ) && is_string( $request_args['method'] ) ) { + $method = $request_args['method']; + } + if ( array_key_exists( 'url', $request_args ) && is_string( $request_args['url'] ) ) { + $url = $request_args['url']; + } + if ( array_key_exists( 'assoc_args', $request_args ) && is_array( $request_args['assoc_args'] ) ) { + $assoc_args = $request_args['assoc_args']; + } + if ( array_key_exists( 'headers', $request_args ) && is_array( $request_args['headers'] ) ) { + $headers = $request_args['headers']; + } + } + + return array( $method, $url, $assoc_args, $headers ); + } + /** * Get Formatter object based on supplied parameters. * diff --git a/tests/RestCommand_Test.php b/tests/RestCommand_Test.php new file mode 100644 index 0000000..7a52e69 --- /dev/null +++ b/tests/RestCommand_Test.php @@ -0,0 +1,99 @@ +command = new RestCommand( 'post', '/wp/v2/posts/(?P[\\d]+)', array() ); + $this->command->set_scope( 'http' ); + + $reflection = new \ReflectionClass( $this->command ); + $this->method = $reflection->getMethod( 'get_http_request_args' ); + $this->method->setAccessible( true ); + } + + public function test_http_request_args_include_basic_auth_header() { + $this->command->set_api_url( 'https://example.com/wp-json' ); + $this->command->set_auth( + array( + 'type' => 'basic', + 'username' => 'wpuser', + 'password' => 'wppass', + ) + ); + + $result = $this->method->invokeArgs( + $this->command, + array( + 'OPTIONS', + '/wp/v2/posts', + array( 'context' => 'edit' ), + ) + ); + + $this->assertSame( 'GET', $result[0] ); + $this->assertSame( 'https://example.com/wp-json/wp/v2/posts', $result[1] ); + $this->assertSame( + array( + 'context' => 'edit', + '_method' => 'OPTIONS', + ), + $result[2] + ); + $this->assertSame( 'Basic d3B1c2VyOndwcGFzcw==', $result[3]['Authorization'] ); + } + + public function test_http_request_args_can_be_filtered_for_custom_auth() { + $this->command->set_api_url( 'https://hooked.example/wp-json' ); + $this->command->set_auth( + array( + 'type' => 'basic', + 'username' => 'old-user', + 'password' => 'old-pass', + ) + ); + + \WP_CLI::add_hook( + 'restful_http_request_args', + function ( $request_args ) { + if ( ! isset( $request_args['url'] ) || 'https://hooked.example/wp-json/wp/v2/posts' !== $request_args['url'] ) { + return $request_args; + } + $request_args['headers'] = array( + 'Authorization' => '******', + 'X-Custom-Auth' => 'jwt', + ); + $request_args['assoc_args'] = array( + 'token' => 'abc123', + ); + return $request_args; + } + ); + + $result = $this->method->invokeArgs( + $this->command, + array( + 'GET', + '/wp/v2/posts', + array(), + ) + ); + + $this->assertSame( + array( + 'Authorization' => '******', + 'X-Custom-Auth' => 'jwt', + ), + $result[3] + ); + $this->assertSame( array( 'token' => 'abc123' ), $result[2] ); + } +} From 64626e0d2d913a57c87d43caa8a3c56e51a36815 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 29 May 2026 14:07:45 +0200 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- inc/RestCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/inc/RestCommand.php b/inc/RestCommand.php index 59a75fb..4065ea0 100644 --- a/inc/RestCommand.php +++ b/inc/RestCommand.php @@ -624,7 +624,10 @@ function ( $a, $b ) { /** * Build HTTP request arguments and allow custom authentication mechanisms. * - * Hook: `restful_http_request_args` + * Runs the `restful_http_request_args` hook with an associative array containing + * `method`, `url`, `assoc_args`, and `headers`, plus the current RestCommand + * instance as the second callback argument. Hook callbacks may return a modified + * array to customize the HTTP request before dispatch. * * @param string $method * @param string $route