From 91ca58f7c6397b96e4035e78ef6c483b33762fc6 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Fri, 21 Mar 2025 11:31:51 +0100 Subject: [PATCH 1/3] Fix ReflectionException in PHPUnit tests Fixes "ReflectionException: Property WP_SQLite_DB::$allow_unsafe_unquoted_parameters does not exist" in WordPress PHPUnit tests. --- .github/workflows/wp-tests-phpunit-run.js | 12 -------- wp-includes/sqlite/class-wp-sqlite-db.php | 37 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/.github/workflows/wp-tests-phpunit-run.js b/.github/workflows/wp-tests-phpunit-run.js index 658f319..e7dc2d6 100644 --- a/.github/workflows/wp-tests-phpunit-run.js +++ b/.github/workflows/wp-tests-phpunit-run.js @@ -22,18 +22,6 @@ const expectedErrors = [ 'Tests_DB_Charset::test_strip_invalid_text', 'Tests_DB::test_db_reconnect', 'Tests_DB::test_get_col_info', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "escaped-false-1"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "escaped-false-2"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "escaped-true-1"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "escaped-true-2"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "format-false-1"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "format-false-2"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "format-true-1"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "format-true-2"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "numbered-false-1"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "numbered-false-2"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "numbered-true-1"', - 'Tests_DB::test_prepare_should_respect_the_allow_unsafe_unquoted_parameters_property with data set "numbered-true-2"', 'Tests_DB::test_process_fields_value_too_long_for_field with data set "invalid chars"', 'Tests_DB::test_process_fields_value_too_long_for_field with data set "too long"', 'Tests_DB::test_process_fields', diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index f98d350..7a1ef29 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -20,6 +20,16 @@ class WP_SQLite_DB extends wpdb { */ protected $dbh; + /** + * Backward compatibility, see wpdb::$allow_unsafe_unquoted_parameters. + * + * This property is mirroring "wpdb::$allow_unsafe_unquoted_parameters", + * because some tests are accessing it externally using PHP reflection. + * + * @var + */ + private $allow_unsafe_unquoted_parameters = true; + /** * Constructor * @@ -289,6 +299,33 @@ public function check_connection( $allow_bail = true ) { return true; } + /** + * Prepares a SQL query for safe execution. + * + * See "wpdb::prepare()". This override only fixes a WPDB test issue. + * + * @param string $query Query statement with `sprintf()`-like placeholders. + * @param array|mixed $args The array of variables or the first variable to substitute. + * @param mixed ...$args Further variables to substitute when using individual arguments. + * @return string|void Sanitized query string, if there is a query to prepare. + */ + public function prepare( $query, ...$args ) { + /* + * Sync "$allow_unsafe_unquoted_parameters" with the WPDB parent property. + * This is only needed because some WPDB tests are accessing the private + * property externally via PHP reflection. This should be fixed WP tests. + */ + $wpdb_allow_unsafe_unquoted_parameters = $this->__get( 'allow_unsafe_unquoted_parameters' ); + if ( $wpdb_allow_unsafe_unquoted_parameters !== $this->allow_unsafe_unquoted_parameters ) { + $property = new ReflectionProperty( 'wpdb', 'allow_unsafe_unquoted_parameters' ); + $property->setAccessible( true ); + $property->setValue( $this, $this->allow_unsafe_unquoted_parameters ); + $property->setAccessible( false ); + } + + return parent::prepare( $query, ...$args ); + } + /** * Performs a database query. * From ad3beb840a52421ea327c76bb4cf8ba7bce00e57 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Mon, 24 Mar 2025 15:24:44 +0100 Subject: [PATCH 2/3] Pass $dbname into WP_SQLite_DB --- .github/workflows/wp-tests-phpunit-run.js | 8 ++------ wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php | 4 ++-- wp-includes/sqlite/class-wp-sqlite-db.php | 10 ++++++---- wp-includes/sqlite/db.php | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/workflows/wp-tests-phpunit-run.js b/.github/workflows/wp-tests-phpunit-run.js index e7dc2d6..f49d691 100644 --- a/.github/workflows/wp-tests-phpunit-run.js +++ b/.github/workflows/wp-tests-phpunit-run.js @@ -11,12 +11,6 @@ const fs = require( 'fs' ); const path = require( 'path' ); const expectedErrors = [ - 'Tests_Admin_wpSiteHealth::test_object_cache_default_thresholds_non_multisite', - 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #0', - 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #1', - 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #2', - 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #3', - 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #4', 'Tests_Comment_WpComment::test_get_instance_should_succeed_for_float_that_is_equal_to_post_id', 'Tests_Cron_getCronArray::test_get_cron_array_output_validation with data set "null"', 'Tests_DB_Charset::test_strip_invalid_text', @@ -33,6 +27,8 @@ const expectedErrors = [ ]; const expectedFailures = [ + 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #2', + 'Tests_Admin_wpSiteHealth::test_object_cache_thresholds with data set #3', 'Tests_Comment::test_wp_new_comment_respects_comment_field_lengths', 'Tests_Comment::test_wp_update_comment', 'Tests_DB_dbDelta::test_spatial_indices', diff --git a/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php b/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php index 53a688d..7878a48 100644 --- a/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php @@ -2,8 +2,8 @@ class WP_SQLite_Crosscheck_DB_ extends WP_SQLite_DB { - public function __construct() { - parent::__construct(); + public function __construct( string $dbname ) { + parent::__construct( $dbname ); $GLOBALS['sqlite'] = $this; $GLOBALS['mysql'] = new wpdb( DB_USER, diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 7a1ef29..21915db 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -31,12 +31,14 @@ class WP_SQLite_DB extends wpdb { private $allow_unsafe_unquoted_parameters = true; /** - * Constructor + * Connects to the SQLite database. * - * Unlike wpdb, no credentials are needed. + * Unlike for MySQL, no credentials and host are needed. + * + * @param string $dbname Database name. */ - public function __construct() { - parent::__construct( '', '', '', '' ); + public function __construct( $dbname ) { + parent::__construct( '', '', $dbname, '' ); $this->charset = 'utf8mb4'; } diff --git a/wp-includes/sqlite/db.php b/wp-includes/sqlite/db.php index 13c2111..9f8ca0a 100644 --- a/wp-includes/sqlite/db.php +++ b/wp-includes/sqlite/db.php @@ -59,7 +59,7 @@ $crosscheck_tests_file_path = dirname( __DIR__, 2 ) . '/tests/class-wp-sqlite-crosscheck-db.php'; if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK && file_exists( $crosscheck_tests_file_path ) ) { require_once $crosscheck_tests_file_path; - $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB(); + $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( DB_NAME ); } else { - $GLOBALS['wpdb'] = new WP_SQLite_DB(); + $GLOBALS['wpdb'] = new WP_SQLite_DB( DB_NAME ); } From d84cc5d5d646b07241a0fa52b9a3da3226adfca6 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 25 Mar 2025 09:43:49 +0100 Subject: [PATCH 3/3] Use PDO::quote() for string escaping --- .../sqlite-ast/class-wp-sqlite-driver.php | 16 ++++++++++++++++ wp-includes/sqlite/class-wp-sqlite-db.php | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php index bfce1e7..110264e 100644 --- a/wp-includes/sqlite-ast/class-wp-sqlite-driver.php +++ b/wp-includes/sqlite-ast/class-wp-sqlite-driver.php @@ -489,6 +489,22 @@ public function get_insert_id() { return $last_insert_id; } + /** + * Quotes a string for use in a query. + * + * Places quotes around the input string (if required) and escapes special + * characters within the input string. See "PDO::quote()". + * + * @param string $value The string value to quote. + * @param int $type The type of the parameter. Default is PDO::PARAM_STR. + * @return string The quoted string. + */ + public function quote( string $value, int $type = PDO::PARAM_STR ): string { + $quoted = addslashes( $value ); + $quoted = $this->pdo->quote( $value, $type ); + return $quoted; + } + /** * Translate and execute a MySQL query in SQLite. * diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 21915db..6c87bb8 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -119,7 +119,13 @@ public function _real_escape( $data ) { if ( ! is_scalar( $data ) ) { return ''; } - $escaped = addslashes( $data ); + if ( $this->dbh instanceof WP_SQLite_Driver ) { + // WP_SQLite_Driver::quote() wraps the escaped string with quotes, + // while WPDB expects the string to be escaped without them. + $escaped = substr( $this->dbh->quote( $data ), 1, -1 ); + } else { + $escaped = addslashes( $data ); + } return $this->add_placeholder_escape( $escaped ); }