Skip to content

Commit 004ebc6

Browse files
committed
Prevent double-processing new option values
1 parent be6a610 commit 004ebc6

File tree

2 files changed

+73
-4
lines changed

2 files changed

+73
-4
lines changed

src/wp-includes/option.php

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,7 @@ function update_option( $option, $value, $autoload = null ) {
927927

928928
/** This filter is documented in wp-includes/option.php */
929929
if ( apply_filters( "default_option_{$option}", false, $option, false ) === $old_value ) {
930-
return add_option( $option, $value, '', $autoload );
930+
return _add_option( $option, $value, $autoload );
931931
}
932932

933933
$serialized_value = maybe_serialize( $value );
@@ -1049,8 +1049,6 @@ function update_option( $option, $value, $autoload = null ) {
10491049
* @since 6.6.0 The $autoload parameter's default value was changed to null.
10501050
* @since 6.7.0 The autoload values 'yes' and 'no' are deprecated.
10511051
*
1052-
* @global wpdb $wpdb WordPress database abstraction object.
1053-
*
10541052
* @param string $option Name of the option to add. Expected to not be SQL-escaped.
10551053
* @param mixed $value Optional. Option value. Must be serializable if non-scalar.
10561054
* Expected to not be SQL-escaped.
@@ -1068,7 +1066,6 @@ function update_option( $option, $value, $autoload = null ) {
10681066
* @return bool True if the option was added, false otherwise.
10691067
*/
10701068
function add_option( $option, $value = '', $deprecated = '', $autoload = null ) {
1071-
global $wpdb;
10721069

10731070
if ( ! empty( $deprecated ) ) {
10741071
_deprecated_argument( __FUNCTION__, '2.3.0' );
@@ -1113,6 +1110,40 @@ function add_option( $option, $value = '', $deprecated = '', $autoload = null )
11131110

11141111
$value = sanitize_option( $option, $value );
11151112

1113+
return _add_option( $option, $value, $autoload );
1114+
}
1115+
1116+
/**
1117+
* Adds a new option.
1118+
*
1119+
* Warning: This is an internal function solely to prevent double-processing the
1120+
* value when update_option() detects the option does not yet exist. You should
1121+
* use add_option() instead. Checks to ensure you aren't adding a protected
1122+
* WordPress option should already have been performed. Do not use those which
1123+
* are protected. The value is expected to be filtered and sanitized.
1124+
*
1125+
* @since X.X.X
1126+
* @access private
1127+
*
1128+
* @global wpdb $wpdb WordPress database abstraction object.
1129+
*
1130+
* @param string $option Name of the option to add. Expected to not be SQL-escaped.
1131+
* @param mixed $value Optional. Option value. Must be serializable if non-scalar.
1132+
* Expected to not be SQL-escaped.
1133+
* @param bool|null $autoload Optional. Whether to load the option when WordPress starts up.
1134+
* Accepts a boolean, or `null` to leave the decision up to default heuristics in WordPress.
1135+
* For backward compatibility 'yes' and 'no' are also accepted.
1136+
* Autoloading too many options can lead to performance problems, especially if the
1137+
* options are not frequently used. For options which are accessed across several places
1138+
* in the frontend, it is recommended to autoload them, by using 'yes'|true.
1139+
* For options which are accessed only on few specific URLs, it is recommended
1140+
* to not autoload them, by using false.
1141+
* Default is null, which means WordPress will determine the autoload value.
1142+
* @return bool True if the option was added, false otherwise.
1143+
*/
1144+
function _add_option( $option, $value = '', $autoload = null ) {
1145+
global $wpdb;
1146+
11161147
/*
11171148
* Make sure the option doesn't already exist.
11181149
* We can check the 'notoptions' cache before we ask for a DB query.

tests/phpunit/tests/option/updateOption.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,48 @@ public function test_update_option_array_with_object() {
219219
$this->assertSame( $num_queries_pre_update, get_num_queries() );
220220
}
221221

222+
/**
223+
* @ticket 21989
224+
*
225+
* @covers ::add_option
226+
* @covers ::add_filter
227+
* @covers ::update_option
228+
* @covers ::remove_filter
229+
* @covers ::get_option
230+
*/
231+
public function test_stored_sanitized_value_from_update_of_nonexistent_option_should_be_same_as_that_from_add_option() {
232+
$before = 'x';
233+
$sanitized = $this->__append_y( $before );
234+
235+
// Add the comparison option, it did not exist before this.
236+
add_filter( 'sanitize_option_doesnotexist_filtered_add', array( $this, '__append_y' ) );
237+
add_option( 'doesnotexist_filtered_add', $before );
238+
remove_filter( 'sanitize_option_doesnotexist_filtered_add', array( $this, '__append_y' ) );
239+
240+
// Add the option, it did not exist before this.
241+
add_filter( 'sanitize_option_doesnotexist_filtered_update', array( $this, '__append_y' ) );
242+
$added = update_option( 'doesnotexist_filtered_update', $before );
243+
remove_filter( 'sanitize_option_doesnotexist_filtered_update', array( $this, '__append_y' ) );
244+
245+
$after = get_option( 'doesnotexist_filtered_update' );
246+
247+
// Check all values match.
248+
$this->assertTrue( $added );
249+
$this->assertSame( get_option( 'doesnotexist_filtered_add' ), $after );
250+
$this->assertSame( $sanitized, $after );
251+
}
252+
222253
/**
223254
* `add_filter()` callback for test_should_respect_default_option_filter_when_option_does_not_yet_exist_in_database().
224255
*/
225256
public function __return_foo() {
226257
return 'foo';
227258
}
259+
260+
/**
261+
* `add_filter()` callback for test_stored_sanitized_value_from_update_of_nonexistent_option_should_be_same_as_that_from_add_option().
262+
*/
263+
public function __append_y( $value ) {
264+
return $value . '_y';
265+
}
228266
}

0 commit comments

Comments
 (0)