From 5387e66f5e3b2dd4e3c5ba0916921dc741d219f3 Mon Sep 17 00:00:00 2001 From: JoshuaGoode <4887830+JoshuaGoode@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:31:12 -0400 Subject: [PATCH] Report database server version --- functions.php | 196 +++++++++++++++++++++++++++++++++++++++++---- prepare.php | 217 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 364 insertions(+), 49 deletions(-) diff --git a/functions.php b/functions.php index 68e875a..8f96189 100644 --- a/functions.php +++ b/functions.php @@ -310,10 +310,175 @@ function upload_results( $results, $rev, $message, $env, $api_key ) { return array( $status_code, $return ); } +/** + * Parse a WordPress-style database host into mysqli connection pieces. + * + * @param string $host Database host string. + * + * @return array|false Parsed connection pieces, or false on invalid input. + */ +function wpt_runner_parse_db_host( $host ) { + $host = (string) $host; + $socket = null; + $port = null; + $is_ipv6 = false; + + if ( '' === $host ) { + return false; + } + + $socket_pos = strpos( $host, ':/' ); + if ( false !== $socket_pos ) { + $socket = substr( $host, $socket_pos + 1 ); + $host = substr( $host, 0, $socket_pos ); + } + + if ( substr_count( $host, ':' ) > 1 ) { + if ( 1 !== preg_match( '/^(?:\[(?P[0-9a-fA-F:.]+)\](?::(?P[0-9]+))?|(?P[0-9a-fA-F:.]+))$/', $host, $matches ) ) { + return false; + } + $parsed_host = ! empty( $matches['host'] ) ? $matches['host'] : ( isset( $matches['host_unbracketed'] ) ? $matches['host_unbracketed'] : '' ); + $is_ipv6 = true; + } else { + if ( 1 !== preg_match( '/^(?P[^:]*)(?::(?P[0-9]+))?$/', $host, $matches ) ) { + return false; + } + $parsed_host = $matches['host']; + } + + if ( '' === $parsed_host ) { + return false; + } + + if ( isset( $matches['port'] ) && '' !== $matches['port'] ) { + $port = (int) $matches['port']; + } + + return array( + 'host' => $parsed_host, + 'port' => $port, + 'socket' => $socket, + 'is_ipv6' => $is_ipv6, + ); +} + +/** + * Gets the database server version, if it can be detected safely. + * + * @param string $db_host Database host. + * @param string $db_user Database user. + * @param string $db_password Database password. + * @param string $db_name Database name. + * + * @return string Raw server version string, or an empty string. + */ +function wpt_runner_get_db_server_version( $db_host, $db_user, $db_password, $db_name ) { + $db_host = trim( (string) $db_host ); + $db_user = trim( (string) $db_user ); + $db_password = (string) $db_password; + $db_name = trim( (string) $db_name ); + + if ( '' === $db_host ) { + $db_host = 'localhost'; + } + + if ( '' === $db_user || '' === $db_name ) { + return ''; + } + + if ( ! class_exists( 'mysqli' ) ) { + return ''; + } + + $required_functions = array( + 'mysqli_close', + 'mysqli_fetch_row', + 'mysqli_free_result', + 'mysqli_init', + 'mysqli_options', + 'mysqli_query', + 'mysqli_real_connect', + ); + + foreach ( $required_functions as $function_name ) { + if ( ! function_exists( $function_name ) ) { + return ''; + } + } + + $parsed_host = wpt_runner_parse_db_host( $db_host ); + if ( false === $parsed_host ) { + return ''; + } + $connect_host = $parsed_host['host']; + // mysqlnd expects IPv6 hosts in brackets, matching WordPress core's connection handling. + if ( $parsed_host['is_ipv6'] && extension_loaded( 'mysqlnd' ) ) { + $connect_host = '[' . $connect_host . ']'; + } + + $mysqli = null; + $mysqli_report_mode = null; + + try { + if ( class_exists( 'mysqli_driver' ) ) { + $mysqli_driver = new mysqli_driver(); + $mysqli_report_mode = $mysqli_driver->report_mode; + } + + if ( function_exists( 'mysqli_report' ) ) { + mysqli_report( MYSQLI_REPORT_OFF ); + } + + $mysqli = mysqli_init(); + if ( false === $mysqli ) { + return ''; + } + + if ( defined( 'MYSQLI_OPT_CONNECT_TIMEOUT' ) ) { + mysqli_options( $mysqli, MYSQLI_OPT_CONNECT_TIMEOUT, 5 ); + } + + if ( ! @mysqli_real_connect( + $mysqli, + $connect_host, + $db_user, + $db_password, + $db_name, + $parsed_host['port'], + $parsed_host['socket'] + ) ) { + return ''; + } + + $result = @mysqli_query( $mysqli, 'SELECT VERSION()' ); + if ( ! is_object( $result ) ) { + return ''; + } + + $row = mysqli_fetch_row( $result ); + mysqli_free_result( $result ); + + if ( ! is_array( $row ) || ! isset( $row[0] ) || '' === (string) $row[0] ) { + return ''; + } + + return (string) $row[0]; + } catch ( Throwable $e ) { + return ''; + } finally { + if ( $mysqli instanceof mysqli ) { + @mysqli_close( $mysqli ); + } + if ( null !== $mysqli_report_mode && function_exists( 'mysqli_report' ) ) { + mysqli_report( $mysqli_report_mode ); + } + } +} + /** * Collects and returns an array of key environment details relevant to the application's context. This includes * the PHP version, installed PHP modules with their versions, system utilities like curl and OpenSSL versions, - * MySQL version, and operating system details. This function is useful for diagnostic purposes, ensuring + * database server version, and operating system details. This function is useful for diagnostic purposes, ensuring * compatibility, or for reporting system configurations in debugging or error logs. * * The function checks for the availability of specific PHP modules and system utilities and captures their versions. @@ -324,12 +489,12 @@ function upload_results( $results, $rev, $message, $env, $api_key ) { * - 'php_version': The current PHP version. * - 'php_modules': An associative array of selected PHP modules and their versions. * - 'system_utils': Versions of certain system utilities such as 'curl', 'imagemagick', 'graphicsmagick', and 'openssl'. - * - 'mysql_version': The version of MySQL installed. + * - 'mysql_version': The version of the database server. * - 'os_name': The name of the operating system. * - 'os_version': The version of the operating system. * * @uses phpversion() to get the PHP version and module versions. - * @uses shell_exec() to execute system commands for retrieving MySQL version, OS details, and versions of utilities like curl and OpenSSL. + * @uses shell_exec() to execute system commands for retrieving OS details and versions of utilities like curl and OpenSSL. * @uses class_exists() to check for the availability of the Imagick and Gmagick classes for version detection. */ function get_env_details() { @@ -343,12 +508,23 @@ function get_env_details() { $imagick_info = Imagick::queryFormats(); } + $WPT_DB_HOST = trim( getenv( 'WPT_DB_HOST' ) ); + if( ! $WPT_DB_HOST ) { + $WPT_DB_HOST = 'localhost'; + } + $WPT_DB_USER = trim( getenv( 'WPT_DB_USER' ) ); + $WPT_DB_PASSWORD = getenv( 'WPT_DB_PASSWORD' ); + if( false === $WPT_DB_PASSWORD ) { + $WPT_DB_PASSWORD = ''; + } + $WPT_DB_NAME = trim( getenv( 'WPT_DB_NAME' ) ); + $env = array( 'php_version' => phpversion(), 'php_modules' => array(), 'gd_info' => $gd_info, 'imagick_info' => $imagick_info, - 'mysql_version' => trim( shell_exec( 'mysql --version' ) ), + 'mysql_version' => wpt_runner_get_db_server_version( $WPT_DB_HOST, $WPT_DB_USER, $WPT_DB_PASSWORD, $WPT_DB_NAME ), 'system_utils' => array(), 'os_name' => trim( shell_exec( 'uname -s' ) ), 'os_version' => trim( shell_exec( 'uname -r' ) ), @@ -400,18 +576,6 @@ function curl_selected_bits($k) { return in_array($k, array('version', 'ssl_vers $curl_bits = curl_version(); $env['system_utils']['curl'] = implode(' ',array_values(array_filter($curl_bits, 'curl_selected_bits',ARRAY_FILTER_USE_KEY) )); - $WPT_DB_HOST = trim( getenv( 'WPT_DB_HOST' ) ); - if( ! $WPT_DB_HOST ) { - $WPT_DB_HOST = 'localhost'; - } - $WPT_DB_USER = trim( getenv( 'WPT_DB_USER' ) ); - $WPT_DB_PASSWORD = trim( getenv( 'WPT_DB_PASSWORD' ) ); - $WPT_DB_NAME = trim( getenv( 'WPT_DB_NAME' ) ); - - //$mysqli = new mysqli( $WPT_DB_HOST, $WPT_DB_USER, $WPT_DB_PASSWORD, $WPT_DB_NAME ); - //$env['mysql_version'] = $mysqli->query("SELECT VERSION()")->fetch_row()[0]; - //$mysqli->close(); - if ( class_exists( 'Imagick' ) ) { $imagick = new Imagick(); $version = $imagick->getVersion(); diff --git a/prepare.php b/prepare.php index b58d7d6..003fc42 100644 --- a/prepare.php +++ b/prepare.php @@ -102,36 +102,186 @@ * Prepares a script to log system information relevant to the testing environment. * The script checks for the existence of the log directory and creates it if it does not exist. * It then collects various pieces of system information including PHP version, loaded PHP modules, - * MySQL version, operating system details, and versions of key utilities like cURL and OpenSSL. + * database server version, operating system details, and versions of key utilities like cURL and OpenSSL. * This information is collected in an array and written to a JSON file in the log directory. * Additionally, if running from the command line during a WordPress installation process, * it outputs the PHP version and executable path. */ -$system_logger = << 1 ) { + if ( 1 !== preg_match( '/^(?:\[(?P[0-9a-fA-F:.]+)\](?::(?P[0-9]+))?|(?P[0-9a-fA-F:.]+))$/', $host, $matches ) ) { + return false; + } + $parsed_host = ! empty( $matches['host'] ) ? $matches['host'] : ( isset( $matches['host_unbracketed'] ) ? $matches['host_unbracketed'] : '' ); + $is_ipv6 = true; + } else { + if ( 1 !== preg_match( '/^(?P[^:]*)(?::(?P[0-9]+))?$/', $host, $matches ) ) { + return false; + } + $parsed_host = $matches['host']; + } + + if ( '' === $parsed_host ) { + return false; + } + + if ( isset( $matches['port'] ) && '' !== $matches['port'] ) { + $port = (int) $matches['port']; + } + + return array( + 'host' => $parsed_host, + 'port' => $port, + 'socket' => $socket, + 'is_ipv6' => $is_ipv6, + ); + } +} +if ( ! function_exists( 'wpt_runner_get_db_server_version' ) ) { + function wpt_runner_get_db_server_version( $db_host, $db_user, $db_password, $db_name ) { + $db_host = trim( (string) $db_host ); + $db_user = trim( (string) $db_user ); + $db_password = (string) $db_password; + $db_name = trim( (string) $db_name ); + + if ( '' === $db_host ) { + $db_host = 'localhost'; + } + + if ( '' === $db_user || '' === $db_name ) { + return ''; + } + + if ( ! class_exists( 'mysqli' ) ) { + return ''; + } + + $required_functions = array( + 'mysqli_close', + 'mysqli_fetch_row', + 'mysqli_free_result', + 'mysqli_init', + 'mysqli_options', + 'mysqli_query', + 'mysqli_real_connect', + ); + + foreach ( $required_functions as $function_name ) { + if ( ! function_exists( $function_name ) ) { + return ''; + } + } + + $parsed_host = wpt_runner_parse_db_host( $db_host ); + if ( false === $parsed_host ) { + return ''; + } + $connect_host = $parsed_host['host']; + // mysqlnd expects IPv6 hosts in brackets, matching WordPress core's connection handling. + if ( $parsed_host['is_ipv6'] && extension_loaded( 'mysqlnd' ) ) { + $connect_host = '[' . $connect_host . ']'; + } + + $mysqli = null; + $mysqli_report_mode = null; + + try { + if ( class_exists( 'mysqli_driver' ) ) { + $mysqli_driver = new mysqli_driver(); + $mysqli_report_mode = $mysqli_driver->report_mode; + } + + if ( function_exists( 'mysqli_report' ) ) { + mysqli_report( MYSQLI_REPORT_OFF ); + } + + $mysqli = mysqli_init(); + if ( false === $mysqli ) { + return ''; + } + + if ( defined( 'MYSQLI_OPT_CONNECT_TIMEOUT' ) ) { + mysqli_options( $mysqli, MYSQLI_OPT_CONNECT_TIMEOUT, 5 ); + } + + if ( ! @mysqli_real_connect( + $mysqli, + $connect_host, + $db_user, + $db_password, + $db_name, + $parsed_host['port'], + $parsed_host['socket'] + ) ) { + return ''; + } + + $result = @mysqli_query( $mysqli, 'SELECT VERSION()' ); + if ( ! is_object( $result ) ) { + return ''; + } + + $row = mysqli_fetch_row( $result ); + mysqli_free_result( $result ); + + if ( ! is_array( $row ) || ! isset( $row[0] ) || '' === (string) $row[0] ) { + return ''; + } + + return (string) $row[0]; + } catch ( Throwable $e ) { + return ''; + } finally { + if ( $mysqli instanceof mysqli ) { + @mysqli_close( $mysqli ); + } + if ( null !== $mysqli_report_mode && function_exists( 'mysqli_report' ) ) { + mysqli_report( $mysqli_report_mode ); + } + } + } +} +$env = array( 'php_version' => phpversion(), 'php_modules' => array(), - 'gd_info' => \$gd_info, - 'imagick_info' => \$imagick_info, - 'mysql_version' => trim( shell_exec( 'mysql --version' ) ), + 'gd_info' => $gd_info, + 'imagick_info' => $imagick_info, + 'mysql_version' => wpt_runner_get_db_server_version( DB_HOST, DB_USER, DB_PASSWORD, DB_NAME ), 'system_utils' => array(), 'os_name' => trim( shell_exec( 'uname -s' ) ), 'os_version' => trim( shell_exec( 'uname -r' ) ), ); -\$php_modules = array( +$php_modules = array( 'bcmath', 'ctype', 'curl', @@ -168,39 +318,40 @@ 'zip', 'zlib', ); -foreach( \$php_modules as \$php_module ) { - \$env['php_modules'][ \$php_module ] = phpversion( \$php_module ); +foreach( $php_modules as $php_module ) { + $env['php_modules'][ $php_module ] = phpversion( $php_module ); } -function curl_selected_bits(\$k) { return in_array(\$k, array('version', 'ssl_version', 'libz_version')); } -\$curl_bits = curl_version(); -\$env['system_utils']['curl'] = implode(' ',array_values(array_filter(\$curl_bits, 'curl_selected_bits',ARRAY_FILTER_USE_KEY) )); +function curl_selected_bits($k) { return in_array($k, array('version', 'ssl_version', 'libz_version')); } +$curl_bits = curl_version(); +$env['system_utils']['curl'] = implode(' ',array_values(array_filter($curl_bits, 'curl_selected_bits',ARRAY_FILTER_USE_KEY) )); if ( class_exists( 'Imagick' ) ) { - \$imagick = new Imagick(); - \$version = \$imagick->getVersion(); - preg_match( '/Magick (\d+\.\d+\.\d+-\d+|\d+\.\d+\.\d+|\d+\.\d+\-\d+|\d+\.\d+)/', \$version['versionString'], \$version ); - \$env['system_utils']['imagemagick'] = \$version[1]; + $imagick = new Imagick(); + $version = $imagick->getVersion(); + preg_match( '/Magick (\d+\.\d+\.\d+-\d+|\d+\.\d+\.\d+|\d+\.\d+\-\d+|\d+\.\d+)/', $version['versionString'], $version ); + $env['system_utils']['imagemagick'] = $version[1]; } elseif ( class_exists( 'Gmagick' ) ) { - \$gmagick = new Gmagick(); - \$version = \$gmagick->getversion(); - preg_match( '/Magick (\d+\.\d+\.\d+-\d+|\d+\.\d+\.\d+|\d+\.\d+\-\d+|\d+\.\d+)/', \$version['versionString'], \$version ); - \$env['system_utils']['graphicsmagick'] = \$version[1]; + $gmagick = new Gmagick(); + $version = $gmagick->getversion(); + preg_match( '/Magick (\d+\.\d+\.\d+-\d+|\d+\.\d+\.\d+|\d+\.\d+\-\d+|\d+\.\d+)/', $version['versionString'], $version ); + $env['system_utils']['graphicsmagick'] = $version[1]; } -\$env['system_utils']['openssl'] = str_replace( 'OpenSSL ', '', trim( shell_exec( 'openssl version' ) ) ); -//\$mysqli = new mysqli( WPT_DB_HOST, WPT_DB_USER, WPT_DB_PASSWORD, WPT_DB_NAME ); -//\$env['mysql_version'] = \$mysqli->query("SELECT VERSION()")->fetch_row()[0]; -//\$mysqli->close(); -file_put_contents( __DIR__ . '/tests/phpunit/build/logs/env.json', json_encode( \$env, JSON_PRETTY_PRINT ) ); +$env['system_utils']['openssl'] = str_replace( 'OpenSSL ', '', trim( shell_exec( 'openssl version' ) ) ); +file_put_contents( __DIR__ . '/tests/phpunit/build/logs/env.json', json_encode( $env, JSON_PRETTY_PRINT ) ); if ( 'cli' === php_sapi_name() && defined( 'WP_INSTALLING' ) && WP_INSTALLING ) { echo PHP_EOL; - echo 'PHP version: ' . phpversion() . ' (' . realpath( \$_SERVER['_'] ) . ')' . PHP_EOL; + echo 'PHP version: ' . phpversion() . ' (' . realpath( $_SERVER['_'] ) . ')' . PHP_EOL; echo PHP_EOL; } EOT; -// Initialize a string that will be used to identify the database settings section in the configuration file. -$logger_replace_string = '// ** Database settings ** //' . PHP_EOL; +// Initialize a string that will be used to identify the post-database-settings section in the configuration file. +$logger_replace_string = 'define( \'DB_COLLATE\', \'\' );' . PHP_EOL; + +if ( false === strpos( $contents, $logger_replace_string ) ) { + error_message( 'Unable to insert the system logger after the database constants in wp-tests-config.php.' ); +} -// Prepend the logger script to the database settings identifier to ensure it gets included in the wp-tests-config.php file. +// Append the logger script to the database settings constants to ensure DB_* constants are available. $system_logger = $logger_replace_string . $system_logger; // Define a string that will set the 'WP_PHP_BINARY' constant to the path of the PHP executable.