diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 4df0b81458..bff57ea329 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -776,47 +776,21 @@ 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 ); - /* * If the new and old values are the same, no need to update. * - * An exception applies when no value is set in the database, i.e. the old value is the default. - * In that case, the new value should always be added as it may be intentional to store it rather than relying on the default. + * Unserialized values will be adequate in most cases. If the unserialized + * data differs, the (maybe) serialized data is checked to avoid + * unnecessary database calls for otherwise identical object instances. * - * See https://core.trac.wordpress.org/ticket/38903 and https://core.trac.wordpress.org/ticket/22192. + * See https://core.trac.wordpress.org/ticket/38903 */ - if ( - $value === $raw_old_value || - ( - $raw_old_value !== $default_value && - _is_equal_database_value( $raw_old_value, $value ) - ) - ) { + if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) { return false; } - if ( $raw_old_value === $default_value ) { - + /** This filter is documented in wp-includes/option.php */ + if ( apply_filters( "default_option_{$option}", false, $option, false ) === $old_value ) { // Default setting for new options is 'yes'. if ( null === $autoload ) { $autoload = 'yes'; @@ -2144,80 +2118,20 @@ function update_network_option( $network_id, $option, $value ) { */ $value = apply_filters( "pre_update_site_option_{$option}", $value, $old_value, $option, $network_id ); - /* - * 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. - */ - global $wp_filter; - - /** This filter is documented in wp-includes/option.php */ - $default_value = apply_filters( "default_site_option_{$option}", false, $option, $network_id ); - - $has_site_filter = has_filter( "pre_site_option_{$option}" ); - $has_option_filter = has_filter( "pre_option_{$option}" ); - if ( $has_site_filter || $has_option_filter ) { - if ( $has_site_filter ) { - $old_ms_filters = $wp_filter[ "pre_site_option_{$option}" ]; - unset( $wp_filter[ "pre_site_option_{$option}" ] ); - } - - if ( $has_option_filter ) { - $old_single_site_filters = $wp_filter[ "pre_option_{$option}" ]; - unset( $wp_filter[ "pre_option_{$option}" ] ); - } - - if ( is_multisite() ) { - $raw_old_value = get_network_option( $network_id, $option ); - } else { - $raw_old_value = get_option( $option, $default_value ); - } - - if ( $has_site_filter ) { - $wp_filter[ "pre_site_option_{$option}" ] = $old_ms_filters; - } - if ( $has_option_filter ) { - $wp_filter[ "pre_option_{$option}" ] = $old_single_site_filters; - } - } else { - $raw_old_value = $old_value; - } - - if ( ! is_multisite() ) { - /** This filter is documented in wp-includes/option.php */ - $default_value = apply_filters( "default_option_{$option}", $default_value, $option, true ); - } - /* * If the new and old values are the same, no need to update. * - * An exception applies when no value is set in the database, i.e. the old value is the default. - * In that case, the new value should always be added as it may be intentional to store it rather than relying on the default. + * Unserialized values will be adequate in most cases. If the unserialized + * data differs, the (maybe) serialized data is checked to avoid + * unnecessary database calls for otherwise identical object instances. * - * See https://core.trac.wordpress.org/ticket/44956 and https://core.trac.wordpress.org/ticket/22192 and https://core.trac.wordpress.org/ticket/59360 + * See https://core.trac.wordpress.org/ticket/44956 */ - if ( - $value === $raw_old_value || - ( - false !== $raw_old_value && - /* - * Single site stores values in the `option_value` field, which cannot be set to NULL. - * This means a PHP `null` value will be cast to an empty string, which can be considered - * equal to values such as an empty string, or false when cast to string. - * - * However, Multisite stores values in the `meta_value` field, which can be set to NULL. - * As NULL is unique in the database, skip checking an old or new value of NULL - * against any other value. - */ - ( ! is_multisite() || ! ( null === $raw_old_value || null === $value ) ) && - _is_equal_database_value( $raw_old_value, $value ) - ) - ) { + if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) { return false; } - if ( $default_value === $raw_old_value ) { + if ( false === $old_value ) { return add_network_option( $network_id, $option, $value ); } @@ -2995,40 +2909,3 @@ function filter_default_option( $default_value, $option, $passed_default ) { return $registered[ $option ]['default']; } - -/** - * Determines whether two values will be equal when stored in the database. - * - * @since 6.4.0 - * @access private - * - * @param mixed $old_value The old value to compare. - * @param mixed $new_value The new value to compare. - * @return bool True if the values are equal, false otherwise. - */ -function _is_equal_database_value( $old_value, $new_value ) { - $values = array( - 'old' => $old_value, - 'new' => $new_value, - ); - - foreach ( $values as $_key => &$_value ) { - // Cast scalars or null to a string so type discrepancies don't result in cache misses. - if ( null === $_value || is_scalar( $_value ) ) { - $_value = (string) $_value; - } - } - - if ( $values['old'] === $values['new'] ) { - return true; - } - - /* - * Unserialized values will be adequate in most cases. If the unserialized - * data differs, the (maybe) serialized data is checked to avoid - * unnecessary database calls for otherwise identical object instances. - * - * See https://core.trac.wordpress.org/ticket/38903 - */ - return maybe_serialize( $old_value ) === maybe_serialize( $new_value ); -} diff --git a/tests/phpunit/tests/option/networkOption.php b/tests/phpunit/tests/option/networkOption.php index 2b0bed6504..bfce484a8d 100644 --- a/tests/phpunit/tests/option/networkOption.php +++ b/tests/phpunit/tests/option/networkOption.php @@ -228,626 +228,4 @@ class Tests_Option_NetworkOption extends WP_UnitTestCase { // Check that no new database queries were performed. $this->assertSame( $num_queries_pre_update, get_num_queries() ); } - - /** - * Tests that update_network_option() triggers one additional query and returns true - * for some loosely equal old and new values when the old value is retrieved from the cache. - * - * The additional query is triggered to update the value in the database. - * - * If the old value is false, the additional queries are triggered to: - * 1. get the old value from the database via get_network_option() -> get_option(). - * 2. (Single Site only) get the old value from the database via update_network_option() -> update_option() -> get_option(). - * 3. update the value in the database via update_network_options() -> update_option(). - * - * @ticket 59360 - * - * @covers ::update_network_option - * - * @dataProvider data_loosely_equal_values_that_should_update - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_network_option_should_update_some_loosely_equal_values_from_cache( $old_value, $new_value ) { - add_network_option( null, 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Comparison will happen against value cached during add_network_option() above. - $updated = update_network_option( null, 'foo', $new_value ); - - $expected_queries = 1; - - if ( false === $old_value ) { - $expected_queries = is_multisite() ? 2 : 3; - } - - $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." ); - $this->assertTrue( $updated, 'update_network_option() should have returned true.' ); - } - - /** - * Tests that update_network_option() triggers two additional queries and returns true - * for some loosely equal old and new values when the old value is retrieved from the database. - * - * The two additional queries are triggered to: - * 1. retrieve the old value from the database, as the option does not exist in the cache. - * 2. update the value in the database. - * - * On Single Site, if the old value is false, the four additional queries are triggered to: - * 1. get the old value from the database via get_network_option() -> get_option(). - * 2. get the alloptions cache via get_network_option() -> get_option(). - * 3. get the old value from the database via update_network_option() -> update_option() -> get_option(). - * 4. update the value in the database via update_network_options() -> update_option(). - * - * @ticket 59360 - * - * @covers ::update_network_option - * - * @dataProvider data_loosely_equal_values_that_should_update - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_network_option_should_update_some_loosely_equal_values_from_db( $old_value, $new_value ) { - add_network_option( null, 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Delete cache. - $network_cache_key = get_current_network_id() . ':foo'; - wp_cache_delete( $network_cache_key, 'site-options' ); - wp_cache_delete( 'alloptions', 'options' ); - - $updated = update_network_option( null, 'foo', $new_value ); - - $expected_queries = false === $old_value && ! is_multisite() ? 4 : 2; - - $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." ); - $this->assertTrue( $updated, 'update_network_option() should have returned true.' ); - } - - /** - * Tests that update_network_option() triggers one additional query and returns true - * for some loosely equal old and new values when the old value is retrieved from a refreshed cache. - * - * The additional query is triggered to update the value in the database. - * - * If the old value is false, the additional queries are triggered to: - * 1. get the old value from the database via get_network_option() -> get_option(). - * 2. get the old value from the database via update_network_option() -> update_option() -> get_option(). - * 3. update the value in the database via update_network_options() -> update_option(). - * - * @ticket 59360 - * - * @covers ::update_network_option - * - * @dataProvider data_loosely_equal_values_that_should_update - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_network_option_should_update_some_loosely_equal_values_from_refreshed_cache( $old_value, $new_value ) { - add_network_option( null, 'foo', $old_value ); - - // Delete and refresh cache from DB. - wp_cache_delete( 'alloptions', 'options' ); - wp_load_alloptions(); - - $num_queries = get_num_queries(); - $updated = update_network_option( null, 'foo', $new_value ); - - $expected_queries = 1; - - if ( false === $old_value ) { - $expected_queries = is_multisite() ? 2 : 3; - } - - $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." ); - $this->assertTrue( $updated, 'update_network_option() should have returned true.' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_loosely_equal_values_that_should_update() { - return array( - // Falsey values. - '(string) "0" to false' => array( '0', false ), - 'empty string to (int) 0' => array( '', 0 ), - 'empty string to (float) 0.0' => array( '', 0.0 ), - '(int) 0 to empty string' => array( 0, '' ), - '(int) 0 to false' => array( 0, false ), - '(float) 0.0 to empty string' => array( 0.0, '' ), - '(float) 0.0 to false' => array( 0.0, false ), - 'false to (string) "0"' => array( false, '0' ), - 'false to (int) 0' => array( false, 0 ), - 'false to (float) 0.0' => array( false, 0.0 ), - - // Non-scalar values. - 'false to array()' => array( false, array() ), - '(string) "false" to array()' => array( 'false', array() ), - 'empty string to array()' => array( '', array() ), - '(int 0) to array()' => array( 0, array() ), - '(string) "0" to array()' => array( '0', array() ), - '(string) "false" to null' => array( 'false', null ), - '(int) 0 to null' => array( 0, null ), - '(string) "0" to null' => array( '0', null ), - 'array() to false' => array( array(), false ), - 'array() to (string) "false"' => array( array(), 'false' ), - 'array() to empty string' => array( array(), '' ), - 'array() to (int) 0' => array( array(), 0 ), - 'array() to (string) "0"' => array( array(), '0' ), - 'array() to null' => array( array(), null ), - ); - } - - /** - * Tests that update_network_option() triggers no additional queries and returns false - * for some values when the old value is retrieved from the cache. - * - * @ticket 59360 - * - * @covers ::update_network_option - * - * @dataProvider data_loosely_equal_values_that_should_not_update - * @dataProvider data_strictly_equal_values - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_network_option_should_not_update_some_values_from_cache( $old_value, $new_value ) { - add_network_option( null, 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Comparison will happen against value cached during add_option() above. - $updated = update_network_option( null, 'foo', $new_value ); - - $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' ); - $this->assertFalse( $updated, 'update_network_option() should have returned false.' ); - } - - /** - * Tests that update_network_option() triggers one additional query and returns false - * for some values when the old value is retrieved from the database. - * - * The additional query is triggered to retrieve the old value from the database. - * - * @ticket 59360 - * - * @covers ::update_network_option - * - * @dataProvider data_loosely_equal_values_that_should_not_update - * @dataProvider data_strictly_equal_values - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_network_option_should_not_update_some_values_from_db( $old_value, $new_value ) { - add_network_option( null, 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Delete cache. - $network_cache_key = get_current_network_id() . ':foo'; - wp_cache_delete( $network_cache_key, 'site-options' ); - wp_cache_delete( 'alloptions', 'options' ); - - $updated = update_network_option( null, 'foo', $new_value ); - - $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run to update the value.' ); - $this->assertFalse( $updated, 'update_network_option() should have returned false.' ); - } - - /** - * Tests that update_network_option() triggers no additional queries and returns false - * for some values when the old value is retrieved from a refreshed cache. - * - * @ticket 59360 - * - * @covers ::update_network_option - * - * @dataProvider data_loosely_equal_values_that_should_not_update - * @dataProvider data_strictly_equal_values - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_network_option_should_not_update_some_values_from_refreshed_cache( $old_value, $new_value ) { - add_network_option( null, 'foo', $old_value ); - - // Delete and refresh cache from DB. - wp_cache_delete( 'alloptions', 'options' ); - wp_load_alloptions(); - - $num_queries = get_num_queries(); - $updated = update_network_option( null, 'foo', $new_value ); - - /* - * Strictly equal old and new values will cause an early return - * with no additional queries. - */ - $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' ); - $this->assertFalse( $updated, 'update_network_option() should have returned false.' ); - } - - /** - * Data provider. - * - * @return array[] - */ - public function data_loosely_equal_values_that_should_not_update() { - return array( - // Truthy values. - '(string) "1" to (int) 1' => array( '1', 1 ), - '(string) "1" to (float) 1.0' => array( '1', 1.0 ), - '(string) "1" to true' => array( '1', true ), - '(int) 1 to (string) "1"' => array( 1, '1' ), - '1 to (float) 1.0' => array( 1, 1.0 ), - '(int) 1 to true' => array( 1, true ), - '(float) 1.0 to (string) "1"' => array( 1.0, '1' ), - '(float) 1.0 to (int) 1' => array( 1.0, 1 ), - '1.0 to true' => array( 1.0, true ), - 'true to (string) "1"' => array( true, '1' ), - 'true to 1' => array( true, 1 ), - 'true to (float) 1.0' => array( true, 1.0 ), - - // Falsey values. - '(string) "0" to (int) 0' => array( '0', 0 ), - '(string) "0" to (float) 0.0' => array( '0', 0.0 ), - '(int) 0 to (string) "0"' => array( 0, '0' ), - '(int) 0 to (float) 0.0' => array( 0, 0.0 ), - '(float) 0.0 to (string) "0"' => array( 0.0, '0' ), - '(float) 0.0 to (int) 0' => array( 0.0, 0 ), - 'empty string to false' => array( '', false ), - - /* - * null as an initial value behaves differently by triggering - * a query, so it is not included in these datasets. - * - * See data_stored_as_empty_string() and its related test. - */ - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_strictly_equal_values() { - $obj = new stdClass(); - - return array( - // Truthy values. - '(string) "1"' => array( '1', '1' ), - '(int) 1' => array( 1, 1 ), - '(float) 1.0' => array( 1.0, 1.0 ), - 'true' => array( true, true ), - 'string with spaces' => array( ' ', ' ' ), - 'non-empty array' => array( array( 'false' ), array( 'false' ) ), - 'object' => array( $obj, $obj ), - - // Falsey values. - '(string) "0"' => array( '0', '0' ), - 'empty string' => array( '', '' ), - '(int) 0' => array( 0, 0 ), - '(float) 0.0' => array( 0.0, 0.0 ), - 'empty array' => array( array(), array() ), - - /* - * false and null are not included in these datasets - * because false is the default value, which triggers - * a call to add_network_option(). - * - * See data_stored_as_empty_string() and its related test. - */ - ); - } - - /** - * Tests that update_network_option() handles a null new value when the new value - * is retrieved from the cache. - * - * On Single Site, this will result in no additional queries as - * the option_value database field is not nullable. - * - * On Multisite, this will result in one additional query as - * the meta_value database field is nullable. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_should_handle_a_null_new_value_from_cache() { - add_network_option( null, 'foo', '' ); - - $num_queries = get_num_queries(); - - // Comparison will happen against value cached during add_option() above. - $updated = update_network_option( null, 'foo', null ); - - $expected_queries = is_multisite() ? 1 : 0; - $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." ); - - if ( is_multisite() ) { - $this->assertTrue( $updated, 'update_network_option() should have returned true.' ); - } else { - $this->assertFalse( $updated, 'update_network_option() should have returned false.' ); - } - } - - /** - * Tests that update_network_option() handles a null new value when the new value - * is retrieved from the database. - * - * On Single Site, this will result in only 1 additional query as - * the option_value database field is not nullable. - * - * On Multisite, this will result in two additional queries as - * the meta_value database field is nullable. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_should_handle_a_null_new_value_from_db() { - add_network_option( null, 'foo', '' ); - - $num_queries = get_num_queries(); - - // Delete cache. - $network_cache_key = get_current_network_id() . ':foo'; - wp_cache_delete( $network_cache_key, 'site-options' ); - wp_cache_delete( 'alloptions', 'options' ); - - $updated = update_network_option( null, 'foo', null ); - - $expected_queries = is_multisite() ? 2 : 1; - $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." ); - - if ( is_multisite() ) { - $this->assertTrue( $updated, 'update_network_option() should have returned true.' ); - } else { - $this->assertFalse( $updated, 'update_network_option() should have returned false.' ); - } - } - - /** - * Tests that update_network_option() handles a null new value when the new value - * is retrieved from a refreshed cache. - * - * On Single Site, this will result in no additional queries as - * the option_value database field is not nullable. - * - * On Multisite, this will result in one additional query as - * the meta_value database field is nullable. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_should_handle_a_null_new_value_from_refreshed_cache() { - add_network_option( null, 'foo', '' ); - - // Delete and refresh cache from DB. - wp_cache_delete( 'alloptions', 'options' ); - wp_load_alloptions(); - - $num_queries = get_num_queries(); - $updated = update_network_option( null, 'foo', null ); - - $expected_queries = is_multisite() ? 1 : 0; - $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." ); - - if ( is_multisite() ) { - $this->assertTrue( $updated, 'update_network_option() should have returned true.' ); - } else { - $this->assertFalse( $updated, 'update_network_option() should have returned false.' ); - } - } - - /** - * Tests that update_network_option() adds a non-existent option when the new value - * is stored as an empty string and false is the default value for the option. - * - * @ticket 59360 - * - * @dataProvider data_stored_as_empty_string - * - * @param mixed $new_value A value that casts to an empty string. - */ - public function test_update_network_option_should_add_network_option_when_the_new_value_is_stored_as_an_empty_string_and_matches_default_value_false( $new_value ) { - global $wpdb; - - if ( is_multisite() ) { - $this->markTestSkipped( 'This test should only run on Single Site.' ); - } - - $this->assertTrue( update_network_option( null, 'foo', $new_value ), 'update_network_option() should have returned true.' ); - - $actual = $wpdb->get_row( "SELECT option_value FROM $wpdb->options WHERE option_name = 'foo' LIMIT 1" ); - - $this->assertIsObject( $actual, 'The option was not added to the database.' ); - $this->assertObjectHasProperty( 'option_value', $actual, 'The "option_value" property was not included.' ); - $this->assertSame( '', $actual->option_value, 'The value was not stored as an empty string.' ); - } - - /** - * Data provider. - * - * @return array[] - */ - public function data_stored_as_empty_string() { - return array( - 'empty string' => array( '' ), - 'null' => array( null ), - ); - } - - /** - * Tests that a non-existent option is added even when its pre filter returns a value. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_with_pre_filter_adds_missing_option() { - $hook_name = is_multisite() ? 'pre_site_option_foo' : 'pre_option_foo'; - - // Force a return value of integer 0. - add_filter( $hook_name, '__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_network_option( null, 'foo', 0 ) ); - } - - /** - * Tests that an existing option is updated even when its pre filter returns the same value. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_with_pre_filter_updates_option_with_different_value() { - $hook_name = is_multisite() ? 'pre_site_option_foo' : 'pre_option_foo'; - - // Add the option with a value of 1 to the database. - update_network_option( null, 'foo', 1 ); - - // Force a return value of integer 0. - add_filter( $hook_name, '__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_network_option( null, 'foo', 0 ) ); - } - - /** - * Tests that calling update_network_option() does not permanently remove pre filters. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_maintains_pre_filters() { - $hook_name = is_multisite() ? 'pre_site_option_foo' : 'pre_option_foo'; - - add_filter( $hook_name, '__return_zero' ); - update_network_option( null, 'foo', 0 ); - - // Assert that the filter is still present. - $this->assertSame( 10, has_filter( $hook_name, '__return_zero' ) ); - } - - /** - * Tests that update_network_option() conditionally applies - * 'pre_site_option_{$option}' and 'pre_option_{$option}' filters. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_should_conditionally_apply_pre_site_option_and_pre_option_filters() { - $option = 'foo'; - $site_hook = new MockAction(); - $option_hook = new MockAction(); - - add_filter( "pre_site_option_{$option}", array( $site_hook, 'filter' ) ); - add_filter( "pre_option_{$option}", array( $option_hook, 'filter' ) ); - - update_network_option( null, $option, 'false' ); - - $this->assertSame( 1, $site_hook->get_call_count(), "'pre_site_option_{$option}' filters occurred an unexpected number of times." ); - $this->assertSame( is_multisite() ? 0 : 1, $option_hook->get_call_count(), "'pre_option_{$option}' filters occurred an unexpected number of times." ); - } - - /** - * Tests that update_network_option() conditionally applies - * 'default_site_{$option}' and 'default_option_{$option}' filters. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_should_conditionally_apply_site_and_option_default_value_filters() { - $option = 'foo'; - $site_hook = new MockAction(); - $option_hook = new MockAction(); - - add_filter( "default_site_option_{$option}", array( $site_hook, 'filter' ) ); - add_filter( "default_option_{$option}", array( $option_hook, 'filter' ) ); - - update_network_option( null, $option, 'false' ); - - $this->assertSame( 2, $site_hook->get_call_count(), "'default_site_option_{$option}' filters occurred an unexpected number of times." ); - $this->assertSame( is_multisite() ? 0 : 2, $option_hook->get_call_count(), "'default_option_{$option}' filters occurred an unexpected number of times." ); - } - - /** - * Tests that update_network_option() adds a non-existent option that uses a filtered default value. - * - * @ticket 59360 - * - * @covers ::update_network_option - */ - public function test_update_network_option_should_add_option_with_filtered_default_value() { - global $wpdb; - - $option = 'foo'; - $default_site_value = 'default-site-value'; - $default_option_value = 'default-option-value'; - - add_filter( - "default_site_option_{$option}", - static function () use ( $default_site_value ) { - return $default_site_value; - } - ); - - add_filter( - "default_option_{$option}", - static function () use ( $default_option_value ) { - return $default_option_value; - } - ); - - /* - * For a non existing option with the unfiltered default of false, passing false here wouldn't work. - * Because the default is different than false here though, passing false is expected to result in - * a database update. - */ - $this->assertTrue( update_network_option( null, $option, false ), 'update_network_option() should have returned true.' ); - - if ( is_multisite() ) { - $actual = $wpdb->get_row( - $wpdb->prepare( - "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s LIMIT 1", - $option - ) - ); - } else { - $actual = $wpdb->get_row( - $wpdb->prepare( - "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", - $option - ) - ); - } - - $value_field = is_multisite() ? 'meta_value' : 'option_value'; - - $this->assertIsObject( $actual, 'The option was not added to the database.' ); - $this->assertObjectHasProperty( $value_field, $actual, "The '$value_field' property was not included." ); - $this->assertSame( '', $actual->$value_field, 'The new value was not stored in the database.' ); - } } diff --git a/tests/phpunit/tests/option/option.php b/tests/phpunit/tests/option/option.php index 04b89c4429..c01479192c 100644 --- a/tests/phpunit/tests/option/option.php +++ b/tests/phpunit/tests/option/option.php @@ -369,415 +369,6 @@ class Tests_Option_Option extends WP_UnitTestCase { ); } - /** - * Tests that update_option() triggers one additional query and returns true - * for some loosely equal old and new values when the old value is retrieved from the cache. - * - * The additional query is triggered to update the value in the database. - * - * @ticket 22192 - * - * @covers ::update_option - * - * @dataProvider data_loosely_equal_values_that_should_update - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_option_should_update_some_loosely_equal_values_from_cache( $old_value, $new_value ) { - add_option( 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Comparison will happen against value cached during add_option() above. - $updated = update_option( 'foo', $new_value ); - - $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run to update the value.' ); - $this->assertTrue( $updated, 'update_option() should have returned true.' ); - } - - /** - * Tests that update_option() triggers two additional queries and returns true - * for some loosely equal old and new values when the old value is retrieved from the database. - * - * The two additional queries are triggered to: - * 1. retrieve the old value from the database, as the option does not exist in the cache. - * 2. update the value in the database. - * - * @ticket 22192 - * - * @covers ::update_option - * - * @dataProvider data_loosely_equal_values_that_should_update - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_option_should_update_some_loosely_equal_values_from_db( $old_value, $new_value ) { - add_option( 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Delete cache. - wp_cache_delete( 'alloptions', 'options' ); - $updated = update_option( 'foo', $new_value ); - - $this->assertSame( 2, get_num_queries() - $num_queries, 'Two additional queries should have run.' ); - $this->assertTrue( $updated, 'update_option() should have returned true.' ); - } - - /** - * Tests that update_option() triggers one additional query and returns true - * for some loosely equal old and new values when the old value is retrieved from a refreshed cache. - * - * The additional query is triggered to update the value in the database. - * - * @ticket 22192 - * - * @covers ::update_option - * - * @dataProvider data_loosely_equal_values_that_should_update - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_option_should_update_some_loosely_equal_values_from_refreshed_cache( $old_value, $new_value ) { - add_option( 'foo', $old_value ); - - // Delete and refresh cache from DB. - wp_cache_delete( 'alloptions', 'options' ); - wp_load_alloptions(); - - $num_queries = get_num_queries(); - $updated = update_option( 'foo', $new_value ); - - $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run to update the value.' ); - $this->assertTrue( $updated, 'update_option() should have returned true.' ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_loosely_equal_values_that_should_update() { - return array( - // Falsey values. - '(string) "0" to false' => array( '0', false ), - 'empty string to (int) 0' => array( '', 0 ), - 'empty string to (float) 0.0' => array( '', 0.0 ), - '(int) 0 to empty string' => array( 0, '' ), - '(int) 0 to false' => array( 0, false ), - '(float) 0.0 to empty string' => array( 0.0, '' ), - '(float) 0.0 to false' => array( 0.0, false ), - 'false to (string) "0"' => array( false, '0' ), - 'false to (int) 0' => array( false, 0 ), - 'false to (float) 0.0' => array( false, 0.0 ), - - // Non-scalar values. - 'false to array()' => array( false, array() ), - '(string) "false" to array()' => array( 'false', array() ), - 'empty string to array()' => array( '', array() ), - '(int 0) to array()' => array( 0, array() ), - '(string) "0" to array()' => array( '0', array() ), - '(string) "false" to null' => array( 'false', null ), - '(int) 0 to null' => array( 0, null ), - '(string) "0" to null' => array( '0', null ), - 'array() to false' => array( array(), false ), - 'array() to (string) "false"' => array( array(), 'false' ), - 'array() to empty string' => array( array(), '' ), - 'array() to (int) 0' => array( array(), 0 ), - 'array() to (string) "0"' => array( array(), '0' ), - 'array() to null' => array( array(), null ), - ); - } - - /** - * Tests that update_option() triggers no additional queries and returns false - * for some values when the old value is retrieved from the cache. - * - * @ticket 22192 - * - * @covers ::update_option - * - * @dataProvider data_loosely_equal_values_that_should_not_update - * @dataProvider data_strictly_equal_values - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_option_should_not_update_some_values_from_cache( $old_value, $new_value ) { - add_option( 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Comparison will happen against value cached during add_option() above. - $updated = update_option( 'foo', $new_value ); - - $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' ); - $this->assertFalse( $updated, 'update_option() should have returned false.' ); - } - - /** - * Tests that update_option() triggers one additional query and returns false - * for some values when the old value is retrieved from the database. - * - * The additional query is triggered to retrieve the old value from the database. - * - * @ticket 22192 - * - * @covers ::update_option - * - * @dataProvider data_loosely_equal_values_that_should_not_update - * @dataProvider data_strictly_equal_values - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_option_should_not_update_some_values_from_db( $old_value, $new_value ) { - add_option( 'foo', $old_value ); - - $num_queries = get_num_queries(); - - // Delete cache. - wp_cache_delete( 'alloptions', 'options' ); - $updated = update_option( 'foo', $new_value ); - - $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run.' ); - $this->assertFalse( $updated, 'update_option() should have returned false.' ); - } - - /** - * Tests that update_option() triggers no additional queries and returns false - * for some values when the old value is retrieved from a refreshed cache. - * - * @ticket 22192 - * - * @covers ::update_option - * - * @dataProvider data_loosely_equal_values_that_should_not_update - * @dataProvider data_strictly_equal_values - * - * @param mixed $old_value The old value. - * @param mixed $new_value The new value to try to set. - */ - public function test_update_option_should_not_update_some_values_from_refreshed_cache( $old_value, $new_value ) { - add_option( 'foo', $old_value ); - - // Delete and refresh cache from DB. - wp_cache_delete( 'alloptions', 'options' ); - wp_load_alloptions(); - - $num_queries = get_num_queries(); - $updated = update_option( 'foo', $new_value ); - - $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' ); - $this->assertFalse( $updated, 'update_option() should have returned false.' ); - } - - /** - * Data provider. - * - * @return array[] - */ - public function data_loosely_equal_values_that_should_not_update() { - return array( - // Truthy values. - '(string) "1" to (int) 1' => array( '1', 1 ), - '(string) "1" to (float) 1.0' => array( '1', 1.0 ), - '(string) "1" to true' => array( '1', true ), - '(int) 1 to (string) "1"' => array( 1, '1' ), - '1 to (float) 1.0' => array( 1, 1.0 ), - '(int) 1 to true' => array( 1, true ), - '(float) 1.0 to (string) "1"' => array( 1.0, '1' ), - '(float) 1.0 to (int) 1' => array( 1.0, 1 ), - '1.0 to true' => array( 1.0, true ), - 'true to (string) "1"' => array( true, '1' ), - 'true to 1' => array( true, 1 ), - 'true to (float) 1.0' => array( true, 1.0 ), - - // Falsey values. - '(string) "0" to (int) 0' => array( '0', 0 ), - '(string) "0" to (float) 0.0' => array( '0', 0.0 ), - '(int) 0 to (string) "0"' => array( 0, '0' ), - '(int) 0 to (float) 0.0' => array( 0, 0.0 ), - '(float) 0.0 to (string) "0"' => array( 0.0, '0' ), - '(float) 0.0 to (int) 0' => array( 0.0, 0 ), - 'empty string to false' => array( '', false ), - 'empty string to null' => array( '', null ), - - /* - * null as an initial value behaves differently by triggering - * a query, so it is not included in these datasets. - * - * See data_stored_as_empty_string() and its related test. - */ - ); - } - - /** - * Data provider. - * - * @return array - */ - public function data_strictly_equal_values() { - $obj = new stdClass(); - - return array( - // Truthy values. - '(string) "1"' => array( '1', '1' ), - '(int) 1' => array( 1, 1 ), - '(float) 1.0' => array( 1.0, 1.0 ), - 'true' => array( true, true ), - 'string with spaces' => array( ' ', ' ' ), - 'non-empty array' => array( array( 'false' ), array( 'false' ) ), - 'object' => array( $obj, $obj ), - - // Falsey values. - '(string) "0"' => array( '0', '0' ), - 'empty string' => array( '', '' ), - '(int) 0' => array( 0, 0 ), - '(float) 0.0' => array( 0.0, 0.0 ), - 'empty array' => array( array(), array() ), - 'false' => array( false, false ), - - /* - * null is not included in these datasets because - * false is the default value, which triggers - * a call to add_option(). - * - * See data_stored_as_empty_string() and its related test. - */ - ); - } - - /** - * Tests that update_option() adds a non-existent option when the new value - * is stored as an empty string and false is the default value for the option. - * - * @ticket 22192 - * - * @dataProvider data_stored_as_empty_string - * - * @param mixed $new_value A value that casts to an empty string. - */ - public function test_update_option_should_add_option_when_the_new_value_is_stored_as_an_empty_string_and_matches_default_value_false( $new_value ) { - global $wpdb; - - $this->assertTrue( update_option( 'foo', $new_value ), 'update_option() should have returned true.' ); - - $actual = $wpdb->get_row( "SELECT option_value FROM $wpdb->options WHERE option_name = 'foo' LIMIT 1" ); - - $this->assertIsObject( $actual, 'The option was not added to the database.' ); - $this->assertObjectHasProperty( 'option_value', $actual, 'The "option_value" property was not included.' ); - $this->assertSame( '', $actual->option_value, 'The value was not stored as an empty string.' ); - } - - /** - * Data provider. - * - * @return array[] - */ - public function data_stored_as_empty_string() { - return array( - 'empty string' => array( '' ), - 'null' => array( null ), - ); - } - - /** - * Tests that update_option() adds a non-existent option that uses a filtered default value. - * - * @ticket 22192 - * - * @covers ::update_option - */ - public function test_update_option_should_add_option_with_filtered_default_value() { - global $wpdb; - - $option = 'update_option_custom_default'; - $default_value = 'default-value'; - - add_filter( - "default_option_{$option}", - static function () use ( $default_value ) { - return $default_value; - } - ); - - /* - * For a non existing option with the unfiltered default of false, passing false here wouldn't work. - * Because the default is different than false here though, passing false is expected to result in - * a database update. - */ - $this->assertTrue( update_option( $option, false ), 'update_option() should have returned true.' ); - - $actual = $wpdb->get_row( - $wpdb->prepare( - "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", - $option - ) - ); - - $this->assertIsObject( $actual, 'The option was not added to the database.' ); - $this->assertObjectHasProperty( 'option_value', $actual, 'The "option_value" property was not included.' ); - $this->assertSame( '', $actual->option_value, 'The new value was not stored in the database.' ); - } - - /** - * Tests that a non-existent 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' ) ); - } - /** * Tests that calling update_option() with changed autoload from 'no' to 'yes' updates the cache correctly. *