Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions src/wp-includes/option.php
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ function update_option( $option, $value, $autoload = null ) {

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

$serialized_value = maybe_serialize( $value );
Expand Down Expand Up @@ -1048,8 +1048,6 @@ function update_option( $option, $value, $autoload = null ) {
* @since 6.6.0 The $autoload parameter's default value was changed to null.
* @since 6.7.0 The autoload values 'yes' and 'no' are deprecated.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $option Name of the option to add. Expected to not be SQL-escaped.
* @param mixed $value Optional. Option value. Must be serializable if non-scalar.
* Expected to not be SQL-escaped.
Expand All @@ -1067,8 +1065,6 @@ function update_option( $option, $value, $autoload = null ) {
* @return bool True if the option was added, false otherwise.
*/
function add_option( $option, $value = '', $deprecated = '', $autoload = null ) {
global $wpdb;

if ( ! empty( $deprecated ) ) {
_deprecated_argument( __FUNCTION__, '2.3.0' );
}
Expand Down Expand Up @@ -1112,6 +1108,41 @@ function add_option( $option, $value = '', $deprecated = '', $autoload = null )

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

return _wp_add_option( $option, $value, $autoload );
}

/**
* Adds a new option.
*
* Warning: This is an internal function solely to prevent double-processing the
* value when update_option() detects the option does not yet exist. You should
* use add_option() instead. Checks to ensure you aren't adding a protected
* WordPress option should already have been performed. Do not use those which
* are protected. The value is expected to be filtered and sanitized.
*
* @since X.X.X
* @internal
* @access private
Comment thread
Roy-Orbison marked this conversation as resolved.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $option Name of the option to add. Expected to not be SQL-escaped.
* @param mixed $value Optional. Option value. Must be serializable if non-scalar.
* Expected to not be SQL-escaped.
* @param bool|null $autoload Optional. Whether to load the option when WordPress starts up.
* Accepts a boolean, or `null` to leave the decision up to default heuristics in WordPress.
* For backward compatibility 'yes' and 'no' are also accepted.
* Autoloading too many options can lead to performance problems, especially if the
* options are not frequently used. For options which are accessed across several places
* in the frontend, it is recommended to autoload them, by using 'yes'|true.
* For options which are accessed only on few specific URLs, it is recommended
* to not autoload them, by using false.
* Default is null, which means WordPress will determine the autoload value.
* @return bool True if the option was added, false otherwise.
*/
function _wp_add_option( $option, $value, $autoload ) {
global $wpdb;

/*
* Make sure the option doesn't already exist.
* We can check the 'notoptions' cache before we ask for a DB query.
Expand Down
40 changes: 40 additions & 0 deletions tests/phpunit/tests/option/updateOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,50 @@ public function test_update_option_array_with_object() {
$this->assertSame( $num_queries_pre_update, get_num_queries() );
}

/**
* @ticket 21989
*
* @covers ::add_option
* @covers ::add_filter
* @covers ::update_option
* @covers ::remove_filter
* @covers ::get_option
*/
public function test_stored_sanitized_value_from_update_of_nonexistent_option_should_be_same_as_that_from_add_option() {
$before = 'cats';
$sanitized = $this->__sanitize_modify( $before );
$sanitize_expected = 'cats and dogs';

// Add the comparison option, it did not exist before this.
add_filter( 'sanitize_option_doesnotexist_filtered_add', array( $this, '__sanitize_modify' ) );
add_option( 'doesnotexist_filtered_add', $before );
remove_filter( 'sanitize_option_doesnotexist_filtered_add', array( $this, '__sanitize_modify' ) );

// Add the option, it did not exist before this.
add_filter( 'sanitize_option_doesnotexist_filtered_update', array( $this, '__sanitize_modify' ) );
$added = update_option( 'doesnotexist_filtered_update', $before );
remove_filter( 'sanitize_option_doesnotexist_filtered_update', array( $this, '__sanitize_modify' ) );

$after = get_option( 'doesnotexist_filtered_update' );

// Check all values match.
$this->assertTrue( $added );
$this->assertSame( get_option( 'doesnotexist_filtered_add' ), $after );
$this->assertSame( $sanitized, $after );
Comment thread
Roy-Orbison marked this conversation as resolved.
$this->assertSame( $sanitize_expected, $after );
}

/**
* `add_filter()` callback for test_should_respect_default_option_filter_when_option_does_not_yet_exist_in_database().
*/
public function __return_foo() {
return 'foo';
}

/**
* `add_filter()` callback for test_stored_sanitized_value_from_update_of_nonexistent_option_should_be_same_as_that_from_add_option().
*/
public function __sanitize_modify( $value ) {
return $value . ' and dogs';
}
}
Loading