From c213c276be1b28605227b1f394b65ab4fa3c9346 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 5 May 2023 18:23:32 +0200 Subject: [PATCH 1/7] Conditionally adapt to Core Requests library --- .../Bootstrap/ExtractDefaultCaCertificate.php | 36 --- .../Bootstrap/IncludeFrameworkAutoloader.php | 3 +- .../Bootstrap/IncludeRequestsAutoloader.php | 107 ++++++++ php/WP_CLI/RequestsLibrary.php | 255 ++++++++++++++++++ php/WP_CLI/Runner.php | 24 +- php/bootstrap.php | 6 +- php/utils.php | 105 ++++---- 7 files changed, 442 insertions(+), 94 deletions(-) delete mode 100644 php/WP_CLI/Bootstrap/ExtractDefaultCaCertificate.php create mode 100644 php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php create mode 100644 php/WP_CLI/RequestsLibrary.php diff --git a/php/WP_CLI/Bootstrap/ExtractDefaultCaCertificate.php b/php/WP_CLI/Bootstrap/ExtractDefaultCaCertificate.php deleted file mode 100644 index fc238335bb..0000000000 --- a/php/WP_CLI/Bootstrap/ExtractDefaultCaCertificate.php +++ /dev/null @@ -1,36 +0,0 @@ - WP_CLI_ROOT . '/php/WP_CLI', 'cli' => WP_CLI_VENDOR_DIR . '/wp-cli/php-cli-tools/lib/cli', - 'WpOrg\Requests' => WP_CLI_VENDOR_DIR . '/rmccue/requests/src', 'Symfony\Component\Finder' => WP_CLI_VENDOR_DIR . '/symfony/finder/', ]; diff --git a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php new file mode 100644 index 0000000000..ddad624d61 --- /dev/null +++ b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php @@ -0,0 +1,107 @@ +find_wp_root(), '/' ); + + // First try to detect a newer Requests version bundled with WordPress. + if ( file_exists( $wp_root . '/wp-includes/Requests/src/Autoload.php' ) ) { + require_once $wp_root . '/wp-includes/Requests/src/Autoload.php'; + + \WpOrg\Requests\Autoload::register(); + + $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CORE ); + + return $state; + } + + // Then see if we can detect the older version bundled with WordPress. + if ( file_exists( $wp_root . '/wp-includes/class-requests.php' ) ) { + require_once $wp_root . '/wp-includes/class-requests.php'; + + \Requests::register_autoloader(); + + $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V1, self::FROM_WP_CORE ); + + return $state; + } + + // Finally, fall back to the Requests version bundled with WP-CLI. + $autoloader = new Autoloader(); + $autoloader->add_namespace( + 'WpOrg\Requests', + WP_CLI_VENDOR_DIR . '/rmccue/requests/src' + ); + + $autoloader->register(); + + \WpOrg\Requests\Autoload::register(); + + $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CLI ); + + return $state; + } + + /** + * Store meta information about the used Requests integration. + * + * This can be used for all the conditional code that needs to work + * across multiple Requests versions. + * + * @param string $class_name The class name of the Requests integration. + * @param string $source The source of the Requests integration. + */ + private function store_requests_meta( $class_name, $source ) { + RequestsLibrary::set_version( $class_name === RequestsLibrary::CLASS_NAME_V2 + ? RequestsLibrary::VERSION_V2 + : RequestsLibrary::VERSION_V1 + ); + RequestsLibrary::set_source( $source ); + RequestsLibrary::set_class_name( $class_name ); + } +} diff --git a/php/WP_CLI/RequestsLibrary.php b/php/WP_CLI/RequestsLibrary.php new file mode 100644 index 0000000000..aff07e3bf3 --- /dev/null +++ b/php/WP_CLI/RequestsLibrary.php @@ -0,0 +1,255 @@ + + */ + const VALID_VERSIONS = [ self::VERSION_V1, self::VERSION_V2 ]; + + /** + * Requests library bundled with WordPress Core being used. + * + * @var string + */ + const SOURCE_WP_CORE = 'wp-core'; + + /** + * Requests library bundled with WP-CLI being used. + * + * @var string + */ + const SOURCE_WP_CLI = 'wp-cli'; + + /** + * Array of valid source for the Requests library. + * + * @var array + */ + const VALID_SOURCES = [ self::SOURCE_WP_CORE, self::SOURCE_WP_CLI ]; + + /** + * Class name of the Requests main class for v1. + * + * @var string + */ + const CLASS_NAME_V1 = '\Requests'; + + /** + * Class name of the Requests main class for v2. + * + * @var string + */ + const CLASS_NAME_V2 = '\WpOrg\Requests\Requests'; + + /** + * Version of the Requests library being used. + * + * @var string + */ + private static $version = self::VERSION_V2; + + /** + * Source of the Requests library being used. + * + * @var string + */ + private static $source = self::SOURCE_WP_CLI; + + /** + * Class name of the Requests library being used. + * + * @var string + */ + private static $class_name = self::CLASS_NAME_V2; + + /** + * Check if the current version is v1. + * + * @return bool Whether the current version is v1. + */ + public static function is_v1() { + return self::get_version() === self::VERSION_V1; + } + + /** + * Check if the current version is v2. + * + * @return bool Whether the current version is v2. + */ + public static function is_v2() { + return self::get_version() === self::VERSION_V2; + } + + /** + * Check if the current source for the Requests library is WordPress Core. + * + * @return bool Whether the current source is WordPress Core. + */ + public static function is_core() { + return self::get_source() === self::SOURCE_WP_CORE; + } + + /** + * Check if the current source for the Requests library is WP-CLI. + * + * @return bool Whether the current source is WP-CLI. + */ + public static function is_cli() { + return self::get_source() === self::SOURCE_WP_CLI; + } + + /** + * Get the current version. + * + * @return string The current version. + */ + public static function get_version() { + return self::$version; + } + + /** + * Set the version of the library. + * + * @param string $version The version to set. + * @throws RuntimeException if the version is invalid. + */ + public static function set_version( $version ) { + if ( ! is_string( $version ) ) { + throw new RuntimeException( 'RequestsLibrary::$version must be a string.' ); + } + + if ( ! in_array( $version, self::VALID_VERSIONS, true ) ) { + throw new RuntimeException( + sprintf( + 'Invalid RequestsLibrary::$version, must be one of: %s.', + implode( ', ', self::VALID_VERSIONS ) + ) + ); + } + + WP_CLI::debug( 'Setting RequestsLibrary::$version to ' . $version, 'bootstrap' ); + + self::$version = $version; + } + + /** + * Get the current class name. + * + * @return string The current class name. + * @throws RuntimeException if the class name is not set. + */ + public static function get_class_name() { + return self::$class_name; + } + + /** + * Set the class name for the library. + * + * @param string $class_name The class name to set. + */ + public static function set_class_name( $class_name ) { + if ( ! is_string( $class_name ) ) { + throw new RuntimeException( 'RequestsLibrary::$class_name must be a string.' ); + } + + WP_CLI::debug( 'Setting RequestsLibrary::$class_name to ' . $class_name, 'bootstrap' ); + + self::$class_name = $class_name; + } + + /** + * Get the current source. + * + * @return string The current source. + */ + public static function get_source() { + return self::$source; + } + + /** + * Set the source of the library. + * + * @param string $source The source to set. + * @throws RuntimeException if the source is invalid. + */ + public static function set_source( $source ) { + if ( ! is_string( $source ) ) { + throw new RuntimeException( 'RequestsLibrary::$source must be a string.' ); + } + + if ( ! in_array( $source, self::VALID_SOURCES, true ) ) { + throw new RuntimeException( + sprintf( + 'Invalid RequestsLibrary::$source, must be one of: %s.', + implode( ', ', self::VALID_SOURCES ) + ) + ); + } + + WP_CLI::debug( 'Setting RequestsLibrary::$source to ' . $source, 'bootstrap' ); + + self::$source = $source; + } + + /** + * Check if a given exception was issued by the Requests library. + * + * This is used because we cannot easily catch multiple different exception + * classes with PHP 5.6. Because of that, we catch generic exceptions, check if they match with + * + * @param Exception $exception Exception to check. + * @return bool Whether the provided exception was issued by the Requests library. + */ + public static function isRequestsException( Exception $exception ) { + return is_a( $exception, '\Requests_Exception' ) + || is_a( $exception, '\WpOrg\Requests\Exception' ); + } + + public static function register_autoloader() { + if ( self::is_v1() && ! class_exists ( self::CLASS_NAME_V1 ) ) { + if ( self::is_core() ) { + require_once ABSPATH . WPINC . '/class-requests.php'; + } else { + require_once WP_CLI_ROOT . '/vendor/rmccue/requests/library/Requests.php'; + } + \Requests::register_autoloader(); + } + + if ( self::is_v2() && ! class_exists( self::CLASS_NAME_V2 ) ) { + if ( self::is_core() ) { + require_once ABSPATH . WPINC . '/Requests/Autoload.php'; + } else { + require_once WP_CLI_ROOT . '/vendor/rmccue/requests/src/Autoload.php'; + } + \WpOrg\Requests\Autoload::register(); + } + } +} diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index a6b89f1859..f104c5c87a 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -247,9 +247,14 @@ private static function extract_subdir_path( $index_path ) { * Find the directory that contains the WordPress files. * Defaults to the current working dir. * - * @return string An absolute path + * @return string An absolute path. */ - private function find_wp_root() { + public function find_wp_root() { + static $wp_root = null; + if ( $wp_root !== null ) { + return $wp_root; + } + if ( isset( $this->config['path'] ) && ( is_bool( $this->config['path'] ) || empty( $this->config['path'] ) ) ) { @@ -262,24 +267,28 @@ private function find_wp_root() { $path = getcwd() . '/' . $path; } - return $path; + $wp_root = $path; + return $wp_root; } if ( $this->cmd_starts_with( [ 'core', 'download' ] ) ) { - return getcwd(); + $wp_root = getcwd(); + return $wp_root; } $dir = getcwd(); while ( is_readable( $dir ) ) { if ( file_exists( "$dir/wp-load.php" ) ) { - return $dir; + $wp_root = $dir; + return $wp_root; } if ( file_exists( "$dir/index.php" ) ) { $path = self::extract_subdir_path( "$dir/index.php" ); if ( ! empty( $path ) ) { - return $path; + $wp_root = $path; + return $wp_root; } } @@ -290,7 +299,8 @@ private function find_wp_root() { $dir = $parent_dir; } - return getcwd(); + $wp_root = getcwd(); + return $wp_root; } /** diff --git a/php/bootstrap.php b/php/bootstrap.php index 94bbe2521e..f0957bcfb4 100644 --- a/php/bootstrap.php +++ b/php/bootstrap.php @@ -22,7 +22,7 @@ function get_bootstrap_steps() { Bootstrap\DeclareAbstractBaseCommand::class, Bootstrap\IncludeFrameworkAutoloader::class, Bootstrap\ConfigureRunner::class, - Bootstrap\ExtractDefaultCaCertificate::class, + Bootstrap\IncludeRequestsAutoloader::class, Bootstrap\InitializeColorization::class, Bootstrap\InitializeLogger::class, Bootstrap\DefineProtectedCommands::class, @@ -75,6 +75,10 @@ function bootstrap() { foreach ( get_bootstrap_steps() as $step ) { /** @var BootstrapStep $step_instance */ + if ( class_exists( 'WP_CLI' ) ) { + \WP_CLI::debug( "Processing bootstrap step: {$step}", 'bootstrap' ); + } + $step_instance = new $step(); $state = $step_instance->process( $state ); } diff --git a/php/utils.php b/php/utils.php index 9b2a12798b..4ae1b619bc 100644 --- a/php/utils.php +++ b/php/utils.php @@ -22,9 +22,7 @@ use WP_CLI\Iterators\Transform; use WP_CLI\NoOp; use WP_CLI\Process; -use WpOrg\Requests\Autoload as RequestsAutoload; -use WpOrg\Requests\Requests; -use WpOrg\Requests\Exception as RequestsException; +use WP_CLI\RequestsLibrary; const PHAR_STREAM_PREFIX = 'phar://'; @@ -747,12 +745,6 @@ static function ( $matches ) use ( $file, $dir ) { * @throws ExitException If the request failed and $halt_on_error is true. */ function http_request( $method, $url, $data = null, $headers = [], $options = [] ) { - - if ( ! class_exists( 'WpOrg\Requests\Hooks' ) ) { - // Autoloader for the Requests library has not been registered yet. - RequestsAutoload::register(); - } - $insecure = isset( $options['insecure'] ) && (bool) $options['insecure']; $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; unset( $options['halt_on_error'] ); @@ -762,53 +754,70 @@ function http_request( $method, $url, $data = null, $headers = [], $options = [] $options['verify'] = ! empty( ini_get( 'curl.cainfo' ) ) ? ini_get( 'curl.cainfo' ) : true; } + RequestsLibrary::register_autoloader(); + + $request_method = [ RequestsLibrary::get_class_name(), 'request' ]; + try { try { - return Requests::request( $url, $headers, $data, $method, $options ); - } catch ( RequestsException $ex ) { - if ( true !== $options['verify'] || 'curlerror' !== $ex->getType() || curl_errno( $ex->getData() ) !== CURLE_SSL_CACERT ) { - throw $ex; + return $request_method( $url, $headers, $data, $method, $options ); + } catch ( Exception $exception ) { + if ( RequestsLibrary::isRequestsException( $exception ) ) { + if ( + true !== $options['verify'] + || 'curlerror' !== $exception->getType() + || curl_errno( $exception->getData() ) !== CURLE_SSL_CACERT + ) { + throw $exception; + } + + $options['verify'] = get_default_cacert( $halt_on_error ); + + return $request_method( $url, $headers, $data, $method, $options ); } - - $options['verify'] = get_default_cacert( $halt_on_error ); - - return Requests::request( $url, $headers, $data, $method, $options ); + throw $exception; } - } catch ( RequestsException $ex ) { - // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. - if ( - ! $insecure - || - 'curlerror' !== $ex->getType() - || - ! in_array( curl_errno( $ex->getData() ), [ CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ], true ) - ) { - $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); + } catch ( Exception $exception ) { + if ( RequestsLibrary::isRequestsException( $exception ) ) { + // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. + if ( + ! $insecure + || + 'curlerror' !== $exception->getType() + || + ! in_array( curl_errno( $exception->getData() ), [ CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ], true ) + ) { + $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $exception->getMessage() ); + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); + } + throw new RuntimeException( $error_msg, null, $exception ); } - throw new RuntimeException( $error_msg, null, $ex ); - } - - $warning = sprintf( - "Re-trying without verify after failing to get verified url '%s' %s.", - $url, - $ex->getMessage() - ); - WP_CLI::warning( $warning ); - - // Disable certificate validation for the next try. - $options['verify'] = false; - try { - return Requests::request( $url, $headers, $data, $method, $options ); - } catch ( RequestsException $ex ) { - $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); - if ( $halt_on_error ) { - WP_CLI::error( $error_msg ); + $warning = sprintf( + "Re-trying without verify after failing to get verified url '%s' %s.", + $url, + $exception->getMessage() + ); + WP_CLI::warning( $warning ); + + // Disable certificate validation for the next try. + $options['verify'] = false; + + try { + return $request_method( $url, $headers, $data, $method, $options ); + } catch ( Exception $exception ) { + if ( RequestsLibrary::isRequestsException( $exception ) ) { + $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $exception->getMessage() ); + if ( $halt_on_error ) { + WP_CLI::error( $error_msg ); + } + throw new RuntimeException( $error_msg, null, $exception ); + } + throw $exception; } - throw new RuntimeException( $error_msg, null, $ex ); } + throw $exception; } } From a8817d12d2f655c4cb626160bb9ee794bd98932f Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 5 May 2023 18:30:39 +0200 Subject: [PATCH 2/7] Add missing docblock --- php/WP_CLI/RequestsLibrary.php | 484 +++++++++++++++++---------------- 1 file changed, 245 insertions(+), 239 deletions(-) diff --git a/php/WP_CLI/RequestsLibrary.php b/php/WP_CLI/RequestsLibrary.php index aff07e3bf3..3e7df8e7f0 100644 --- a/php/WP_CLI/RequestsLibrary.php +++ b/php/WP_CLI/RequestsLibrary.php @@ -13,243 +13,249 @@ */ final class RequestsLibrary { - /** - * Version 1 of the Requests library. - * - * @var string - */ - const VERSION_V1 = 'v1'; - - /** - * Version 2 of the Requests library. - * - * @var string - */ - const VERSION_V2 = 'v2'; - - /** - * Array of valid versions for the Requests library. - * - * @var array - */ - const VALID_VERSIONS = [ self::VERSION_V1, self::VERSION_V2 ]; - - /** - * Requests library bundled with WordPress Core being used. - * - * @var string - */ - const SOURCE_WP_CORE = 'wp-core'; - - /** - * Requests library bundled with WP-CLI being used. - * - * @var string - */ - const SOURCE_WP_CLI = 'wp-cli'; - - /** - * Array of valid source for the Requests library. - * - * @var array - */ - const VALID_SOURCES = [ self::SOURCE_WP_CORE, self::SOURCE_WP_CLI ]; - - /** - * Class name of the Requests main class for v1. - * - * @var string - */ - const CLASS_NAME_V1 = '\Requests'; - - /** - * Class name of the Requests main class for v2. - * - * @var string - */ - const CLASS_NAME_V2 = '\WpOrg\Requests\Requests'; - - /** - * Version of the Requests library being used. - * - * @var string - */ - private static $version = self::VERSION_V2; - - /** - * Source of the Requests library being used. - * - * @var string - */ - private static $source = self::SOURCE_WP_CLI; - - /** - * Class name of the Requests library being used. - * - * @var string - */ - private static $class_name = self::CLASS_NAME_V2; - - /** - * Check if the current version is v1. - * - * @return bool Whether the current version is v1. - */ - public static function is_v1() { - return self::get_version() === self::VERSION_V1; - } - - /** - * Check if the current version is v2. - * - * @return bool Whether the current version is v2. - */ - public static function is_v2() { - return self::get_version() === self::VERSION_V2; - } - - /** - * Check if the current source for the Requests library is WordPress Core. - * - * @return bool Whether the current source is WordPress Core. - */ - public static function is_core() { - return self::get_source() === self::SOURCE_WP_CORE; - } - - /** - * Check if the current source for the Requests library is WP-CLI. - * - * @return bool Whether the current source is WP-CLI. - */ - public static function is_cli() { - return self::get_source() === self::SOURCE_WP_CLI; - } - - /** - * Get the current version. - * - * @return string The current version. - */ - public static function get_version() { - return self::$version; - } - - /** - * Set the version of the library. - * - * @param string $version The version to set. - * @throws RuntimeException if the version is invalid. - */ - public static function set_version( $version ) { - if ( ! is_string( $version ) ) { - throw new RuntimeException( 'RequestsLibrary::$version must be a string.' ); - } - - if ( ! in_array( $version, self::VALID_VERSIONS, true ) ) { - throw new RuntimeException( - sprintf( - 'Invalid RequestsLibrary::$version, must be one of: %s.', - implode( ', ', self::VALID_VERSIONS ) - ) - ); - } - - WP_CLI::debug( 'Setting RequestsLibrary::$version to ' . $version, 'bootstrap' ); - - self::$version = $version; - } - - /** - * Get the current class name. - * - * @return string The current class name. - * @throws RuntimeException if the class name is not set. - */ - public static function get_class_name() { - return self::$class_name; - } - - /** - * Set the class name for the library. - * - * @param string $class_name The class name to set. - */ - public static function set_class_name( $class_name ) { - if ( ! is_string( $class_name ) ) { - throw new RuntimeException( 'RequestsLibrary::$class_name must be a string.' ); - } - - WP_CLI::debug( 'Setting RequestsLibrary::$class_name to ' . $class_name, 'bootstrap' ); - - self::$class_name = $class_name; - } - - /** - * Get the current source. - * - * @return string The current source. - */ - public static function get_source() { - return self::$source; - } - - /** - * Set the source of the library. - * - * @param string $source The source to set. - * @throws RuntimeException if the source is invalid. - */ - public static function set_source( $source ) { - if ( ! is_string( $source ) ) { - throw new RuntimeException( 'RequestsLibrary::$source must be a string.' ); - } - - if ( ! in_array( $source, self::VALID_SOURCES, true ) ) { - throw new RuntimeException( - sprintf( - 'Invalid RequestsLibrary::$source, must be one of: %s.', - implode( ', ', self::VALID_SOURCES ) - ) - ); - } - - WP_CLI::debug( 'Setting RequestsLibrary::$source to ' . $source, 'bootstrap' ); - - self::$source = $source; - } - - /** - * Check if a given exception was issued by the Requests library. - * - * This is used because we cannot easily catch multiple different exception - * classes with PHP 5.6. Because of that, we catch generic exceptions, check if they match with - * - * @param Exception $exception Exception to check. - * @return bool Whether the provided exception was issued by the Requests library. - */ - public static function isRequestsException( Exception $exception ) { - return is_a( $exception, '\Requests_Exception' ) - || is_a( $exception, '\WpOrg\Requests\Exception' ); - } - - public static function register_autoloader() { - if ( self::is_v1() && ! class_exists ( self::CLASS_NAME_V1 ) ) { - if ( self::is_core() ) { - require_once ABSPATH . WPINC . '/class-requests.php'; - } else { - require_once WP_CLI_ROOT . '/vendor/rmccue/requests/library/Requests.php'; - } - \Requests::register_autoloader(); - } - - if ( self::is_v2() && ! class_exists( self::CLASS_NAME_V2 ) ) { - if ( self::is_core() ) { - require_once ABSPATH . WPINC . '/Requests/Autoload.php'; - } else { - require_once WP_CLI_ROOT . '/vendor/rmccue/requests/src/Autoload.php'; - } - \WpOrg\Requests\Autoload::register(); - } - } + /** + * Version 1 of the Requests library. + * + * @var string + */ + const VERSION_V1 = 'v1'; + + /** + * Version 2 of the Requests library. + * + * @var string + */ + const VERSION_V2 = 'v2'; + + /** + * Array of valid versions for the Requests library. + * + * @var array + */ + const VALID_VERSIONS = [ self::VERSION_V1, self::VERSION_V2 ]; + + /** + * Requests library bundled with WordPress Core being used. + * + * @var string + */ + const SOURCE_WP_CORE = 'wp-core'; + + /** + * Requests library bundled with WP-CLI being used. + * + * @var string + */ + const SOURCE_WP_CLI = 'wp-cli'; + + /** + * Array of valid source for the Requests library. + * + * @var array + */ + const VALID_SOURCES = [ self::SOURCE_WP_CORE, self::SOURCE_WP_CLI ]; + + /** + * Class name of the Requests main class for v1. + * + * @var string + */ + const CLASS_NAME_V1 = '\Requests'; + + /** + * Class name of the Requests main class for v2. + * + * @var string + */ + const CLASS_NAME_V2 = '\WpOrg\Requests\Requests'; + + /** + * Version of the Requests library being used. + * + * @var string + */ + private static $version = self::VERSION_V2; + + /** + * Source of the Requests library being used. + * + * @var string + */ + private static $source = self::SOURCE_WP_CLI; + + /** + * Class name of the Requests library being used. + * + * @var string + */ + private static $class_name = self::CLASS_NAME_V2; + + /** + * Check if the current version is v1. + * + * @return bool Whether the current version is v1. + */ + public static function is_v1() { + return self::get_version() === self::VERSION_V1; + } + + /** + * Check if the current version is v2. + * + * @return bool Whether the current version is v2. + */ + public static function is_v2() { + return self::get_version() === self::VERSION_V2; + } + + /** + * Check if the current source for the Requests library is WordPress Core. + * + * @return bool Whether the current source is WordPress Core. + */ + public static function is_core() { + return self::get_source() === self::SOURCE_WP_CORE; + } + + /** + * Check if the current source for the Requests library is WP-CLI. + * + * @return bool Whether the current source is WP-CLI. + */ + public static function is_cli() { + return self::get_source() === self::SOURCE_WP_CLI; + } + + /** + * Get the current version. + * + * @return string The current version. + */ + public static function get_version() { + return self::$version; + } + + /** + * Set the version of the library. + * + * @param string $version The version to set. + * @throws RuntimeException if the version is invalid. + */ + public static function set_version( $version ) { + if ( ! is_string( $version ) ) { + throw new RuntimeException( 'RequestsLibrary::$version must be a string.' ); + } + + if ( ! in_array( $version, self::VALID_VERSIONS, true ) ) { + throw new RuntimeException( + sprintf( + 'Invalid RequestsLibrary::$version, must be one of: %s.', + implode( ', ', self::VALID_VERSIONS ) + ) + ); + } + + WP_CLI::debug( 'Setting RequestsLibrary::$version to ' . $version, 'bootstrap' ); + + self::$version = $version; + } + + /** + * Get the current class name. + * + * @return string The current class name. + * @throws RuntimeException if the class name is not set. + */ + public static function get_class_name() { + return self::$class_name; + } + + /** + * Set the class name for the library. + * + * @param string $class_name The class name to set. + */ + public static function set_class_name( $class_name ) { + if ( ! is_string( $class_name ) ) { + throw new RuntimeException( 'RequestsLibrary::$class_name must be a string.' ); + } + + WP_CLI::debug( 'Setting RequestsLibrary::$class_name to ' . $class_name, 'bootstrap' ); + + self::$class_name = $class_name; + } + + /** + * Get the current source. + * + * @return string The current source. + */ + public static function get_source() { + return self::$source; + } + + /** + * Set the source of the library. + * + * @param string $source The source to set. + * @throws RuntimeException if the source is invalid. + */ + public static function set_source( $source ) { + if ( ! is_string( $source ) ) { + throw new RuntimeException( 'RequestsLibrary::$source must be a string.' ); + } + + if ( ! in_array( $source, self::VALID_SOURCES, true ) ) { + throw new RuntimeException( + sprintf( + 'Invalid RequestsLibrary::$source, must be one of: %s.', + implode( ', ', self::VALID_SOURCES ) + ) + ); + } + + WP_CLI::debug( 'Setting RequestsLibrary::$source to ' . $source, 'bootstrap' ); + + self::$source = $source; + } + + /** + * Check if a given exception was issued by the Requests library. + * + * This is used because we cannot easily catch multiple different exception + * classes with PHP 5.6. Because of that, we catch generic exceptions, check if they match with + * + * @param Exception $exception Exception to check. + * @return bool Whether the provided exception was issued by the Requests library. + */ + public static function isRequestsException( Exception $exception ) { + return is_a( $exception, '\Requests_Exception' ) + || is_a( $exception, '\WpOrg\Requests\Exception' ); + } + + /** + * Register the autoloader for the Requests library. + * + * This checks for the detected setup and register the corresponding + * autoloader if it is still needed. + */ + public static function register_autoloader() { + if ( self::is_v1() && ! class_exists ( self::CLASS_NAME_V1 ) ) { + if ( self::is_core() ) { + require_once ABSPATH . WPINC . '/class-requests.php'; + } else { + require_once WP_CLI_ROOT . '/vendor/rmccue/requests/library/Requests.php'; + } + \Requests::register_autoloader(); + } + + if ( self::is_v2() && ! class_exists( self::CLASS_NAME_V2 ) ) { + if ( self::is_core() ) { + require_once ABSPATH . WPINC . '/Requests/Autoload.php'; + } else { + require_once WP_CLI_ROOT . '/vendor/rmccue/requests/src/Autoload.php'; + } + \WpOrg\Requests\Autoload::register(); + } + } } From 817265182ddb15b721df38268d72c8cb00fe0fbe Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 5 May 2023 18:31:41 +0200 Subject: [PATCH 3/7] Fix PHPCS issues --- php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php index ddad624d61..9f478d298b 100644 --- a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php +++ b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php @@ -80,7 +80,7 @@ public function process( BootstrapState $state ) { $autoloader->register(); - \WpOrg\Requests\Autoload::register(); + \WpOrg\Requests\Autoload::register(); $this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CLI ); @@ -97,11 +97,11 @@ public function process( BootstrapState $state ) { * @param string $source The source of the Requests integration. */ private function store_requests_meta( $class_name, $source ) { - RequestsLibrary::set_version( $class_name === RequestsLibrary::CLASS_NAME_V2 - ? RequestsLibrary::VERSION_V2 - : RequestsLibrary::VERSION_V1 - ); - RequestsLibrary::set_source( $source ); - RequestsLibrary::set_class_name( $class_name ); + RequestsLibrary::set_version( $class_name === RequestsLibrary::CLASS_NAME_V2 + ? RequestsLibrary::VERSION_V2 + : RequestsLibrary::VERSION_V1 + ); + RequestsLibrary::set_source( $source ); + RequestsLibrary::set_class_name( $class_name ); } } From ea3d6951988220e5756a907c3ca38ddcc3675185 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 5 May 2023 18:38:34 +0200 Subject: [PATCH 4/7] Fix PHPCS issues --- php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php | 9 +++++---- php/WP_CLI/RequestsLibrary.php | 9 +++++---- php/WP_CLI/Runner.php | 2 +- php/utils.php | 10 +++++----- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php index 9f478d298b..2c22a85433 100644 --- a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php +++ b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php @@ -94,12 +94,13 @@ public function process( BootstrapState $state ) { * across multiple Requests versions. * * @param string $class_name The class name of the Requests integration. - * @param string $source The source of the Requests integration. + * @param string $source The source of the Requests integration. */ private function store_requests_meta( $class_name, $source ) { - RequestsLibrary::set_version( $class_name === RequestsLibrary::CLASS_NAME_V2 - ? RequestsLibrary::VERSION_V2 - : RequestsLibrary::VERSION_V1 + RequestsLibrary::set_version( + RequestsLibrary::CLASS_NAME_V2 === $class_name + ? RequestsLibrary::VERSION_V2 + : RequestsLibrary::VERSION_V1 ); RequestsLibrary::set_source( $source ); RequestsLibrary::set_class_name( $class_name ); diff --git a/php/WP_CLI/RequestsLibrary.php b/php/WP_CLI/RequestsLibrary.php index 3e7df8e7f0..98e08ffafc 100644 --- a/php/WP_CLI/RequestsLibrary.php +++ b/php/WP_CLI/RequestsLibrary.php @@ -46,7 +46,7 @@ final class RequestsLibrary { * * @var string */ - const SOURCE_WP_CLI = 'wp-cli'; + const SOURCE_WP_CLI = 'wp-cli'; /** * Array of valid source for the Requests library. @@ -223,12 +223,13 @@ public static function set_source( $source ) { * Check if a given exception was issued by the Requests library. * * This is used because we cannot easily catch multiple different exception - * classes with PHP 5.6. Because of that, we catch generic exceptions, check if they match with + * classes with PHP 5.6. Because of that, we catch generic exceptions, check if + * they match the Requests library, and re-throw them if they do not. * * @param Exception $exception Exception to check. * @return bool Whether the provided exception was issued by the Requests library. */ - public static function isRequestsException( Exception $exception ) { + public static function is_requests_exception( Exception $exception ) { return is_a( $exception, '\Requests_Exception' ) || is_a( $exception, '\WpOrg\Requests\Exception' ); } @@ -240,7 +241,7 @@ public static function isRequestsException( Exception $exception ) { * autoloader if it is still needed. */ public static function register_autoloader() { - if ( self::is_v1() && ! class_exists ( self::CLASS_NAME_V1 ) ) { + if ( self::is_v1() && ! class_exists( self::CLASS_NAME_V1 ) ) { if ( self::is_core() ) { require_once ABSPATH . WPINC . '/class-requests.php'; } else { diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index f104c5c87a..5924ccdc9d 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -251,7 +251,7 @@ private static function extract_subdir_path( $index_path ) { */ public function find_wp_root() { static $wp_root = null; - if ( $wp_root !== null ) { + if ( null !== $wp_root ) { return $wp_root; } diff --git a/php/utils.php b/php/utils.php index 4ae1b619bc..cb8c2c21f7 100644 --- a/php/utils.php +++ b/php/utils.php @@ -762,7 +762,7 @@ function http_request( $method, $url, $data = null, $headers = [], $options = [] try { return $request_method( $url, $headers, $data, $method, $options ); } catch ( Exception $exception ) { - if ( RequestsLibrary::isRequestsException( $exception ) ) { + if ( RequestsLibrary::is_requests_exception( $exception ) ) { if ( true !== $options['verify'] || 'curlerror' !== $exception->getType() @@ -770,15 +770,15 @@ function http_request( $method, $url, $data = null, $headers = [], $options = [] ) { throw $exception; } - + $options['verify'] = get_default_cacert( $halt_on_error ); - + return $request_method( $url, $headers, $data, $method, $options ); } throw $exception; } } catch ( Exception $exception ) { - if ( RequestsLibrary::isRequestsException( $exception ) ) { + if ( RequestsLibrary::is_requests_exception( $exception ) ) { // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. if ( ! $insecure @@ -807,7 +807,7 @@ function http_request( $method, $url, $data = null, $headers = [], $options = [] try { return $request_method( $url, $headers, $data, $method, $options ); } catch ( Exception $exception ) { - if ( RequestsLibrary::isRequestsException( $exception ) ) { + if ( RequestsLibrary::is_requests_exception( $exception ) ) { $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $exception->getMessage() ); if ( $halt_on_error ) { WP_CLI::error( $error_msg ); From 881fba4fff5cb67afde0516e6b576f4f107f82ee Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 5 May 2023 19:15:24 +0200 Subject: [PATCH 5/7] Adapt Requests feature tests --- features/requests.feature | 48 +++++---------------------------------- 1 file changed, 6 insertions(+), 42 deletions(-) diff --git a/features/requests.feature b/features/requests.feature index fd208b4061..1bf24ebfe5 100644 --- a/features/requests.feature +++ b/features/requests.feature @@ -56,18 +56,7 @@ Feature: Requests integration with both v1 and v2 When I run `wp eval 'var_dump( \WP_CLI\Utils\http_request( "GET", "https://example.com/" ) );'` Then STDOUT should contain: """ - object(WpOrg\Requests\Response) - """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should be empty - - When I run `wp eval 'var_dump( \WpOrg\Requests\Requests::get( "https://example.com/" ) );'` - Then STDOUT should contain: - """ - object(WpOrg\Requests\Response) + object(Requests_Response) """ And STDOUT should contain: """ @@ -75,18 +64,13 @@ Feature: Requests integration with both v1 and v2 """ And STDERR should be empty - When I run `wp eval 'var_dump( \Requests::get( "https://example.com/" ) );'` + When I run `wp plugin install duplicate-post` Then STDOUT should contain: """ - object(WpOrg\Requests\Response) + Success: Installed 1 of 1 plugins. """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should be empty - Scenario: Current version with WordPress-bundled Requests v2 + Scenario: Current version with WordPress-bundled Requests v2 Given a WP installation And I run `wp core update --version=6.2 --force` @@ -107,28 +91,8 @@ Feature: Requests integration with both v1 and v2 """ And STDERR should be empty - When I run `wp eval 'var_dump( \WpOrg\Requests\Requests::get( "https://example.com/" ) );'` + When I run `wp plugin install duplicate-post` Then STDOUT should contain: """ - object(WpOrg\Requests\Response) - """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should be empty - - # Expect a deprecation warning here. - When I try `wp eval 'var_dump( \Requests::get( "https://example.com/" ) );'` - Then STDOUT should contain: - """ - object(WpOrg\Requests\Response) - """ - And STDOUT should contain: - """ - HTTP/1.1 200 OK - """ - And STDERR should contain: - """ - The PSR-0 `Requests_...` class names in the Requests library are deprecated. + Success: Installed 1 of 1 plugins. """ From 2b218539b08aa4698a593ffeddefd16ae7733be3 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Fri, 5 May 2023 19:21:45 +0200 Subject: [PATCH 6/7] Revert static caching of $wp_root, it is dynamically adapted when using aliases --- php/WP_CLI/Runner.php | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 5924ccdc9d..e77a6b90dc 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -250,11 +250,6 @@ private static function extract_subdir_path( $index_path ) { * @return string An absolute path. */ public function find_wp_root() { - static $wp_root = null; - if ( null !== $wp_root ) { - return $wp_root; - } - if ( isset( $this->config['path'] ) && ( is_bool( $this->config['path'] ) || empty( $this->config['path'] ) ) ) { @@ -267,28 +262,24 @@ public function find_wp_root() { $path = getcwd() . '/' . $path; } - $wp_root = $path; - return $wp_root; + return $path; } if ( $this->cmd_starts_with( [ 'core', 'download' ] ) ) { - $wp_root = getcwd(); - return $wp_root; + return getcwd(); } $dir = getcwd(); while ( is_readable( $dir ) ) { if ( file_exists( "$dir/wp-load.php" ) ) { - $wp_root = $dir; - return $wp_root; + return $dir; } if ( file_exists( "$dir/index.php" ) ) { $path = self::extract_subdir_path( "$dir/index.php" ); if ( ! empty( $path ) ) { - $wp_root = $path; - return $wp_root; + return $path; } } @@ -299,8 +290,7 @@ public function find_wp_root() { $dir = $parent_dir; } - $wp_root = getcwd(); - return $wp_root; + return getcwd(); } /** From 541a2382ae51c60cf0416aa30334610466c14f3e Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Thu, 11 May 2023 14:37:24 +0200 Subject: [PATCH 7/7] Fix invalid -path regression --- php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php index 2c22a85433..f369ba362b 100644 --- a/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php +++ b/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php @@ -46,7 +46,16 @@ public function process( BootstrapState $state ) { return; } - $runner = new RunnerInstance(); + $runner = new RunnerInstance(); + + // Make sure we don't deal with an invalid `--path` value. + $config = $runner()->config; + if ( isset( $config['path'] ) && + ( is_bool( $config['path'] ) || empty( $config['path'] ) ) + ) { + return $state; + } + $wp_root = rtrim( $runner()->find_wp_root(), '/' ); // First try to detect a newer Requests version bundled with WordPress.