From f188c8706b08e75b4b5441ad60f32fd5de9b4408 Mon Sep 17 00:00:00 2001 From: Felix Arntz Date: Tue, 26 Sep 2023 09:17:13 -0500 Subject: [PATCH] Fix bug when comparing options that have a pre filter. --- src/wp-includes/option.php | 22 ++++++++++- tests/phpunit/tests/option/option.php | 55 ++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 2beb5eeef36d5..e6e276cc1f18b 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -776,6 +776,24 @@ function update_option( $option, $value, $autoload = null ) { */ $value = apply_filters( 'pre_update_option', $value, $option, $old_value ); + /* + * To get the actual raw old value from the database, any existing pre filters need to be temporarily disabled. + * Immediately after getting the raw value, they are reinstated. + * The raw value is only used to determine whether a value is present in the database. It is not used anywhere + * else, and is not passed to any of the hooks either. + */ + if ( has_filter( "pre_option_{$option}" ) ) { + global $wp_filter; + + $old_filters = $wp_filter[ "pre_option_{$option}" ]; + unset( $wp_filter[ "pre_option_{$option}" ] ); + + $raw_old_value = get_option( $option ); + $wp_filter[ "pre_option_{$option}" ] = $old_filters; + } else { + $raw_old_value = $old_value; + } + /** This filter is documented in wp-includes/option.php */ $default_value = apply_filters( "default_option_{$option}", false, $option, false ); @@ -787,11 +805,11 @@ function update_option( $option, $value, $autoload = null ) { * * See https://core.trac.wordpress.org/ticket/38903 and https://core.trac.wordpress.org/ticket/22192. */ - if ( $old_value !== $default_value && _is_equal_database_value( $old_value, $value ) ) { + if ( $raw_old_value !== $default_value && _is_equal_database_value( $raw_old_value, $value ) ) { return false; } - if ( $old_value === $default_value ) { + if ( $raw_old_value === $default_value ) { // Default setting for new options is 'yes'. if ( null === $autoload ) { diff --git a/tests/phpunit/tests/option/option.php b/tests/phpunit/tests/option/option.php index 0cef91d6d754b..e2e4a8abe954d 100644 --- a/tests/phpunit/tests/option/option.php +++ b/tests/phpunit/tests/option/option.php @@ -439,7 +439,6 @@ public function test_update_loosey_options_from_refreshed_cache( $old_value, $ne } } - /** * Data provider. * @@ -581,4 +580,58 @@ static function () use ( $default_value ) { $this->assertSame( 1, $actual ); } + + /** + * Tests that a non-existing option is added even when its pre filter returns a value. + * + * @ticket 22192 + * + * @covers ::update_option + */ + public function test_update_option_with_pre_filter_adds_missing_option() { + // Force a return value of integer 0. + add_filter( 'pre_option_foo', '__return_zero' ); + + /* + * This should succeed, since the 'foo' option does not exist in the database. + * The default value is false, so it differs from 0. + */ + $this->assertTrue( update_option( 'foo', 0 ) ); + } + + /** + * Tests that an existing option is updated even when its pre filter returns the same value. + * + * @ticket 22192 + * + * @covers ::update_option + */ + public function test_update_option_with_pre_filter_updates_option_with_different_value() { + // Add the option with a value of 1 to the database. + add_option( 'foo', 1 ); + + // Force a return value of integer 0. + add_filter( 'pre_option_foo', '__return_zero' ); + + /* + * This should succeed, since the 'foo' option has a value of 1 in the database. + * Therefore it differs from 0 and should be updated. + */ + $this->assertTrue( update_option( 'foo', 0 ) ); + } + + /** + * Tests that calling update_option() does not permanently remove pre filters. + * + * @ticket 22192 + * + * @covers ::update_option + */ + public function test_update_option_maintains_pre_filters() { + add_filter( 'pre_option_foo', '__return_zero' ); + update_option( 'foo', 0 ); + + // Assert that the filter is still present. + $this->assertSame( 10, has_filter( 'pre_option_foo', '__return_zero' ) ); + } }