Skip to content

Commit 319e6b8

Browse files
committed
Options, Meta APIs: Fix follow up bug when comparing values for options using the pre_option_{$option} filter.
This fix is relevant for options such as `gmt_offset` that use a filter to force a specific value regardless of what is stored in the database. Props mamaduka, flixos90, mukesh27, spacedmonkey. See #22192. git-svn-id: https://develop.svn.wordpress.org/trunk@56717 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 6e63524 commit 319e6b8

File tree

2 files changed

+74
-3
lines changed

2 files changed

+74
-3
lines changed

src/wp-includes/option.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,24 @@ function update_option( $option, $value, $autoload = null ) {
776776
*/
777777
$value = apply_filters( 'pre_update_option', $value, $option, $old_value );
778778

779+
/*
780+
* To get the actual raw old value from the database, any existing pre filters need to be temporarily disabled.
781+
* Immediately after getting the raw value, they are reinstated.
782+
* The raw value is only used to determine whether a value is present in the database. It is not used anywhere
783+
* else, and is not passed to any of the hooks either.
784+
*/
785+
if ( has_filter( "pre_option_{$option}" ) ) {
786+
global $wp_filter;
787+
788+
$old_filters = $wp_filter[ "pre_option_{$option}" ];
789+
unset( $wp_filter[ "pre_option_{$option}" ] );
790+
791+
$raw_old_value = get_option( $option );
792+
$wp_filter[ "pre_option_{$option}" ] = $old_filters;
793+
} else {
794+
$raw_old_value = $old_value;
795+
}
796+
779797
/** This filter is documented in wp-includes/option.php */
780798
$default_value = apply_filters( "default_option_{$option}", false, $option, false );
781799

@@ -787,11 +805,11 @@ function update_option( $option, $value, $autoload = null ) {
787805
*
788806
* See https://core.trac.wordpress.org/ticket/38903 and https://core.trac.wordpress.org/ticket/22192.
789807
*/
790-
if ( $old_value !== $default_value && _is_equal_database_value( $old_value, $value ) ) {
808+
if ( $raw_old_value !== $default_value && _is_equal_database_value( $raw_old_value, $value ) ) {
791809
return false;
792810
}
793811

794-
if ( $old_value === $default_value ) {
812+
if ( $raw_old_value === $default_value ) {
795813

796814
// Default setting for new options is 'yes'.
797815
if ( null === $autoload ) {

tests/phpunit/tests/option/option.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,6 @@ public function test_update_loosey_options_from_refreshed_cache( $old_value, $ne
439439
}
440440
}
441441

442-
443442
/**
444443
* Data provider.
445444
*
@@ -581,4 +580,58 @@ static function () use ( $default_value ) {
581580

582581
$this->assertSame( 1, $actual );
583582
}
583+
584+
/**
585+
* Tests that a non-existing option is added even when its pre filter returns a value.
586+
*
587+
* @ticket 22192
588+
*
589+
* @covers ::update_option
590+
*/
591+
public function test_update_option_with_pre_filter_adds_missing_option() {
592+
// Force a return value of integer 0.
593+
add_filter( 'pre_option_foo', '__return_zero' );
594+
595+
/*
596+
* This should succeed, since the 'foo' option does not exist in the database.
597+
* The default value is false, so it differs from 0.
598+
*/
599+
$this->assertTrue( update_option( 'foo', 0 ) );
600+
}
601+
602+
/**
603+
* Tests that an existing option is updated even when its pre filter returns the same value.
604+
*
605+
* @ticket 22192
606+
*
607+
* @covers ::update_option
608+
*/
609+
public function test_update_option_with_pre_filter_updates_option_with_different_value() {
610+
// Add the option with a value of 1 to the database.
611+
add_option( 'foo', 1 );
612+
613+
// Force a return value of integer 0.
614+
add_filter( 'pre_option_foo', '__return_zero' );
615+
616+
/*
617+
* This should succeed, since the 'foo' option has a value of 1 in the database.
618+
* Therefore it differs from 0 and should be updated.
619+
*/
620+
$this->assertTrue( update_option( 'foo', 0 ) );
621+
}
622+
623+
/**
624+
* Tests that calling update_option() does not permanently remove pre filters.
625+
*
626+
* @ticket 22192
627+
*
628+
* @covers ::update_option
629+
*/
630+
public function test_update_option_maintains_pre_filters() {
631+
add_filter( 'pre_option_foo', '__return_zero' );
632+
update_option( 'foo', 0 );
633+
634+
// Assert that the filter is still present.
635+
$this->assertSame( 10, has_filter( 'pre_option_foo', '__return_zero' ) );
636+
}
584637
}

0 commit comments

Comments
 (0)