Make WordPress Core

Changeset 56946

Timestamp:
10/16/2023 08:35:05 PM (10 months ago)
Author:
joemcgill
Message:

Options, Meta APIs: Revert update_option changes.

This reverts changes from [56648], [56681], [56717], [56762], [56788], [56797], and [56814] to restore the behavior for update_option() and update_network_option() to their prior state as of 6.3.X due to the discovery of various backwards compatibility issues found late in the 6.4 release cycle.

Props mukesh27, costdev, flixos90, spacedmonkey, snicco, jrf, joemcgill.
See #22192, #59360.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/option.php

    r56817 r56946  
    778778
    779779    /*
    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;
     780     * If the new and old values are the same, no need to update.
     781     *
     782     * Unserialized values will be adequate in most cases. If the unserialized
     783     * data differs, the (maybe) serialized data is checked to avoid
     784     * unnecessary database calls for otherwise identical object instances.
     785     *
     786     * See https://core.trac.wordpress.org/ticket/38903
     787     */
     788    if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) {
     789        return false;
    795790    }
    796791
    797792    /** This filter is documented in wp-includes/option.php */
    798     $default_value = apply_filters( "default_option_{$option}", false, $option, false );
    799 
    800     /*
    801      * If the new and old values are the same, no need to update.
    802      *
    803      * An exception applies when no value is set in the database, i.e. the old value is the default.
    804      * In that case, the new value should always be added as it may be intentional to store it rather than relying on the default.
    805      *
    806      * See https://core.trac.wordpress.org/ticket/38903 and https://core.trac.wordpress.org/ticket/22192.
    807      */
    808     if (
    809         $value === $raw_old_value ||
    810         (
    811             $raw_old_value !== $default_value &&
    812             _is_equal_database_value( $raw_old_value, $value )
    813         )
    814     ) {
    815         return false;
    816     }
    817 
    818     if ( $raw_old_value === $default_value ) {
    819 
     793    if ( apply_filters( "default_option_{$option}", false, $option, false ) === $old_value ) {
    820794        // Default setting for new options is 'yes'.
    821795        if ( null === $autoload ) {
     
    21462120
    21472121    /*
    2148      * To get the actual raw old value from the database, any existing pre filters need to be temporarily disabled.
    2149      * Immediately after getting the raw value, they are reinstated.
    2150      * The raw value is only used to determine whether a value is present in the database. It is not used anywhere
    2151      * else, and is not passed to any of the hooks either.
    2152      */
    2153     global $wp_filter;
    2154 
    2155     /** This filter is documented in wp-includes/option.php */
    2156     $default_value = apply_filters( "default_site_option_{$option}", false, $option, $network_id );
    2157 
    2158     $has_site_filter   = has_filter( "pre_site_option_{$option}" );
    2159     $has_option_filter = has_filter( "pre_option_{$option}" );
    2160     if ( $has_site_filter || $has_option_filter ) {
    2161         if ( $has_site_filter ) {
    2162             $old_ms_filters = $wp_filter[ "pre_site_option_{$option}" ];
    2163             unset( $wp_filter[ "pre_site_option_{$option}" ] );
    2164         }
    2165 
    2166         if ( $has_option_filter ) {
    2167             $old_single_site_filters = $wp_filter[ "pre_option_{$option}" ];
    2168             unset( $wp_filter[ "pre_option_{$option}" ] );
    2169         }
    2170 
    2171         if ( is_multisite() ) {
    2172             $raw_old_value = get_network_option( $network_id, $option );
    2173         } else {
    2174             $raw_old_value = get_option( $option, $default_value );
    2175         }
    2176 
    2177         if ( $has_site_filter ) {
    2178             $wp_filter[ "pre_site_option_{$option}" ] = $old_ms_filters;
    2179         }
    2180         if ( $has_option_filter ) {
    2181             $wp_filter[ "pre_option_{$option}" ] = $old_single_site_filters;
    2182         }
    2183     } else {
    2184         $raw_old_value = $old_value;
    2185     }
    2186 
    2187     if ( ! is_multisite() ) {
    2188         /** This filter is documented in wp-includes/option.php */
    2189         $default_value = apply_filters( "default_option_{$option}", $default_value, $option, true );
    2190     }
    2191 
    2192     /*
    21932122     * If the new and old values are the same, no need to update.
    21942123     *
    2195      * An exception applies when no value is set in the database, i.e. the old value is the default.
    2196      * In that case, the new value should always be added as it may be intentional to store it rather than relying on the default.
    2197      *
    2198      * See https://core.trac.wordpress.org/ticket/44956 and https://core.trac.wordpress.org/ticket/22192 and https://core.trac.wordpress.org/ticket/59360
    2199      */
    2200     if (
    2201         $value === $raw_old_value ||
    2202         (
    2203             false !== $raw_old_value &&
    2204             /*
    2205              * Single site stores values in the `option_value` field, which cannot be set to NULL.
    2206              * This means a PHP `null` value will be cast to an empty string, which can be considered
    2207              * equal to values such as an empty string, or false when cast to string.
    2208              *
    2209              * However, Multisite stores values in the `meta_value` field, which can be set to NULL.
    2210              * As NULL is unique in the database, skip checking an old or new value of NULL
    2211              * against any other value.
    2212              */
    2213             ( ! is_multisite() || ! ( null === $raw_old_value || null === $value ) ) &&
    2214             _is_equal_database_value( $raw_old_value, $value )
    2215         )
    2216     ) {
     2124     * Unserialized values will be adequate in most cases. If the unserialized
     2125     * data differs, the (maybe) serialized data is checked to avoid
     2126     * unnecessary database calls for otherwise identical object instances.
     2127     *
     2128     * See https://core.trac.wordpress.org/ticket/44956
     2129     */
     2130    if ( $value === $old_value || maybe_serialize( $value ) === maybe_serialize( $old_value ) ) {
    22172131        return false;
    22182132    }
    22192133
    2220     if ( $default_value === $raw_old_value ) {
     2134    if ( old_value ) {
    22212135        return add_network_option( $network_id, $option, $value );
    22222136    }
     
    29962910    return $registered[ $option ]['default'];
    29972911}
    2998 
    2999 /**
    3000  * Determines whether two values will be equal when stored in the database.
    3001  *
    3002  * @since 6.4.0
    3003  * @access private
    3004  *
    3005  * @param mixed $old_value The old value to compare.
    3006  * @param mixed $new_value The new value to compare.
    3007  * @return bool True if the values are equal, false otherwise.
    3008  */
    3009 function _is_equal_database_value( $old_value, $new_value ) {
    3010     $values = array(
    3011         'old' => $old_value,
    3012         'new' => $new_value,
    3013     );
    3014 
    3015     foreach ( $values as $_key => &$_value ) {
    3016         // Cast scalars or null to a string so type discrepancies don't result in cache misses.
    3017         if ( null === $_value || is_scalar( $_value ) ) {
    3018             $_value = (string) $_value;
    3019         }
    3020     }
    3021 
    3022     if ( $values['old'] === $values['new'] ) {
    3023         return true;
    3024     }
    3025 
    3026     /*
    3027      * Unserialized values will be adequate in most cases. If the unserialized
    3028      * data differs, the (maybe) serialized data is checked to avoid
    3029      * unnecessary database calls for otherwise identical object instances.
    3030      *
    3031      * See https://core.trac.wordpress.org/ticket/38903
    3032      */
    3033     return maybe_serialize( $old_value ) === maybe_serialize( $new_value );
    3034 }
  • trunk/tests/phpunit/tests/option/networkOption.php

    r56814 r56946  
    229229        $this->assertSame( $num_queries_pre_update, get_num_queries() );
    230230    }
    231 
    232     /**
    233      * Tests that update_network_option() triggers one additional query and returns true
    234      * for some loosely equal old and new values when the old value is retrieved from the cache.
    235      *
    236      * The additional query is triggered to update the value in the database.
    237      *
    238      * If the old value is false, the additional queries are triggered to:
    239      * 1. get the old value from the database via get_network_option() -> get_option().
    240      * 2. (Single Site only) get the old value from the database via update_network_option() -> update_option() -> get_option().
    241      * 3. update the value in the database via update_network_options() -> update_option().
    242      *
    243      * @ticket 59360
    244      *
    245      * @covers ::update_network_option
    246      *
    247      * @dataProvider data_loosely_equal_values_that_should_update
    248      *
    249      * @param mixed $old_value The old value.
    250      * @param mixed $new_value The new value to try to set.
    251      */
    252     public function test_update_network_option_should_update_some_loosely_equal_values_from_cache( $old_value, $new_value ) {
    253         add_network_option( null, 'foo', $old_value );
    254 
    255         $num_queries = get_num_queries();
    256 
    257         // Comparison will happen against value cached during add_network_option() above.
    258         $updated = update_network_option( null, 'foo', $new_value );
    259 
    260         $expected_queries = 1;
    261 
    262         if ( false === $old_value ) {
    263             $expected_queries = is_multisite() ? 2 : 3;
    264         }
    265 
    266         $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." );
    267         $this->assertTrue( $updated, 'update_network_option() should have returned true.' );
    268     }
    269 
    270     /**
    271      * Tests that update_network_option() triggers two additional queries and returns true
    272      * for some loosely equal old and new values when the old value is retrieved from the database.
    273      *
    274      * The two additional queries are triggered to:
    275      * 1. retrieve the old value from the database, as the option does not exist in the cache.
    276      * 2. update the value in the database.
    277      *
    278      * On Single Site, if the old value is false, the four additional queries are triggered to:
    279      * 1. get the old value from the database via get_network_option() -> get_option().
    280      * 2. get the alloptions cache via get_network_option() -> get_option().
    281      * 3. get the old value from the database via update_network_option() -> update_option() -> get_option().
    282      * 4. update the value in the database via update_network_options() -> update_option().
    283      *
    284      * @ticket 59360
    285      *
    286      * @covers ::update_network_option
    287      *
    288      * @dataProvider data_loosely_equal_values_that_should_update
    289      *
    290      * @param mixed $old_value The old value.
    291      * @param mixed $new_value The new value to try to set.
    292      */
    293     public function test_update_network_option_should_update_some_loosely_equal_values_from_db( $old_value, $new_value ) {
    294         add_network_option( null, 'foo', $old_value );
    295 
    296         $num_queries = get_num_queries();
    297 
    298         // Delete cache.
    299         $network_cache_key = get_current_network_id() . ':foo';
    300         wp_cache_delete( $network_cache_key, 'site-options' );
    301         wp_cache_delete( 'alloptions', 'options' );
    302 
    303         $updated = update_network_option( null, 'foo', $new_value );
    304 
    305         $expected_queries = false === $old_value && ! is_multisite() ? 4 : 2;
    306 
    307         $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." );
    308         $this->assertTrue( $updated, 'update_network_option() should have returned true.' );
    309     }
    310 
    311     /**
    312      * Tests that update_network_option() triggers one additional query and returns true
    313      * for some loosely equal old and new values when the old value is retrieved from a refreshed cache.
    314      *
    315      * The additional query is triggered to update the value in the database.
    316      *
    317      * If the old value is false, the additional queries are triggered to:
    318      * 1. get the old value from the database via get_network_option() -> get_option().
    319      * 2. get the old value from the database via update_network_option() -> update_option() -> get_option().
    320      * 3. update the value in the database via update_network_options() -> update_option().
    321      *
    322      * @ticket 59360
    323      *
    324      * @covers ::update_network_option
    325      *
    326      * @dataProvider data_loosely_equal_values_that_should_update
    327      *
    328      * @param mixed $old_value The old value.
    329      * @param mixed $new_value The new value to try to set.
    330      */
    331     public function test_update_network_option_should_update_some_loosely_equal_values_from_refreshed_cache( $old_value, $new_value ) {
    332         add_network_option( null, 'foo', $old_value );
    333 
    334         // Delete and refresh cache from DB.
    335         wp_cache_delete( 'alloptions', 'options' );
    336         wp_load_alloptions();
    337 
    338         $num_queries = get_num_queries();
    339         $updated     = update_network_option( null, 'foo', $new_value );
    340 
    341         $expected_queries = 1;
    342 
    343         if ( false === $old_value ) {
    344             $expected_queries = is_multisite() ? 2 : 3;
    345         }
    346 
    347         $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." );
    348         $this->assertTrue( $updated, 'update_network_option() should have returned true.' );
    349     }
    350 
    351     /**
    352      * Data provider.
    353      *
    354      * @return array
    355      */
    356     public function data_loosely_equal_values_that_should_update() {
    357         return array(
    358             // Falsey values.
    359             '(string) "0" to false'       => array( '0', false ),
    360             'empty string to (int) 0'     => array( '', 0 ),
    361             'empty string to (float) 0.0' => array( '', 0.0 ),
    362             '(int) 0 to empty string'     => array( 0, '' ),
    363             '(int) 0 to false'            => array( 0, false ),
    364             '(float) 0.0 to empty string' => array( 0.0, '' ),
    365             '(float) 0.0 to false'        => array( 0.0, false ),
    366             'false to (string) "0"'       => array( false, '0' ),
    367             'false to (int) 0'            => array( false, 0 ),
    368             'false to (float) 0.0'        => array( false, 0.0 ),
    369 
    370             // Non-scalar values.
    371             'false to array()'            => array( false, array() ),
    372             '(string) "false" to array()' => array( 'false', array() ),
    373             'empty string to array()'     => array( '', array() ),
    374             '(int 0) to array()'          => array( 0, array() ),
    375             '(string) "0" to array()'     => array( '0', array() ),
    376             '(string) "false" to null'    => array( 'false', null ),
    377             '(int) 0 to null'             => array( 0, null ),
    378             '(string) "0" to null'        => array( '0', null ),
    379             'array() to false'            => array( array(), false ),
    380             'array() to (string) "false"' => array( array(), 'false' ),
    381             'array() to empty string'     => array( array(), '' ),
    382             'array() to (int) 0'          => array( array(), 0 ),
    383             'array() to (string) "0"'     => array( array(), '0' ),
    384             'array() to null'             => array( array(), null ),
    385         );
    386     }
    387 
    388     /**
    389      * Tests that update_network_option() triggers no additional queries and returns false
    390      * for some values when the old value is retrieved from the cache.
    391      *
    392      * @ticket 59360
    393      *
    394      * @covers ::update_network_option
    395      *
    396      * @dataProvider data_loosely_equal_values_that_should_not_update
    397      * @dataProvider data_strictly_equal_values
    398      *
    399      * @param mixed $old_value The old value.
    400      * @param mixed $new_value The new value to try to set.
    401      */
    402     public function test_update_network_option_should_not_update_some_values_from_cache( $old_value, $new_value ) {
    403         add_network_option( null, 'foo', $old_value );
    404 
    405         $num_queries = get_num_queries();
    406 
    407         // Comparison will happen against value cached during add_option() above.
    408         $updated = update_network_option( null, 'foo', $new_value );
    409 
    410         $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' );
    411         $this->assertFalse( $updated, 'update_network_option() should have returned false.' );
    412     }
    413 
    414     /**
    415      * Tests that update_network_option() triggers one additional query and returns false
    416      * for some values when the old value is retrieved from the database.
    417      *
    418      * The additional query is triggered to retrieve the old value from the database.
    419      *
    420      * @ticket 59360
    421      *
    422      * @covers ::update_network_option
    423      *
    424      * @dataProvider data_loosely_equal_values_that_should_not_update
    425      * @dataProvider data_strictly_equal_values
    426      *
    427      * @param mixed $old_value The old value.
    428      * @param mixed $new_value The new value to try to set.
    429      */
    430     public function test_update_network_option_should_not_update_some_values_from_db( $old_value, $new_value ) {
    431         add_network_option( null, 'foo', $old_value );
    432 
    433         $num_queries = get_num_queries();
    434 
    435         // Delete cache.
    436         $network_cache_key = get_current_network_id() . ':foo';
    437         wp_cache_delete( $network_cache_key, 'site-options' );
    438         wp_cache_delete( 'alloptions', 'options' );
    439 
    440         $updated = update_network_option( null, 'foo', $new_value );
    441 
    442         $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run to update the value.' );
    443         $this->assertFalse( $updated, 'update_network_option() should have returned false.' );
    444     }
    445 
    446     /**
    447      * Tests that update_network_option() triggers no additional queries and returns false
    448      * for some values when the old value is retrieved from a refreshed cache.
    449      *
    450      * @ticket 59360
    451      *
    452      * @covers ::update_network_option
    453      *
    454      * @dataProvider data_loosely_equal_values_that_should_not_update
    455      * @dataProvider data_strictly_equal_values
    456      *
    457      * @param mixed $old_value The old value.
    458      * @param mixed $new_value The new value to try to set.
    459      */
    460     public function test_update_network_option_should_not_update_some_values_from_refreshed_cache( $old_value, $new_value ) {
    461         add_network_option( null, 'foo', $old_value );
    462 
    463         // Delete and refresh cache from DB.
    464         wp_cache_delete( 'alloptions', 'options' );
    465         wp_load_alloptions();
    466 
    467         $num_queries = get_num_queries();
    468         $updated     = update_network_option( null, 'foo', $new_value );
    469 
    470         /*
    471          * Strictly equal old and new values will cause an early return
    472          * with no additional queries.
    473          */
    474         $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' );
    475         $this->assertFalse( $updated, 'update_network_option() should have returned false.' );
    476     }
    477 
    478     /**
    479      * Data provider.
    480      *
    481      * @return array[]
    482      */
    483     public function data_loosely_equal_values_that_should_not_update() {
    484         return array(
    485             // Truthy values.
    486             '(string) "1" to (int) 1'     => array( '1', 1 ),
    487             '(string) "1" to (float) 1.0' => array( '1', 1.0 ),
    488             '(string) "1" to true'        => array( '1', true ),
    489             '(int) 1 to (string) "1"'     => array( 1, '1' ),
    490             '1 to (float) 1.0'            => array( 1, 1.0 ),
    491             '(int) 1 to true'             => array( 1, true ),
    492             '(float) 1.0 to (string) "1"' => array( 1.0, '1' ),
    493             '(float) 1.0 to (int) 1'      => array( 1.0, 1 ),
    494             '1.0 to true'                 => array( 1.0, true ),
    495             'true to (string) "1"'        => array( true, '1' ),
    496             'true to 1'                   => array( true, 1 ),
    497             'true to (float) 1.0'         => array( true, 1.0 ),
    498 
    499             // Falsey values.
    500             '(string) "0" to (int) 0'     => array( '0', 0 ),
    501             '(string) "0" to (float) 0.0' => array( '0', 0.0 ),
    502             '(int) 0 to (string) "0"'     => array( 0, '0' ),
    503             '(int) 0 to (float) 0.0'      => array( 0, 0.0 ),
    504             '(float) 0.0 to (string) "0"' => array( 0.0, '0' ),
    505             '(float) 0.0 to (int) 0'      => array( 0.0, 0 ),
    506             'empty string to false'       => array( '', false ),
    507 
    508             /*
    509              * null as an initial value behaves differently by triggering
    510              * a query, so it is not included in these datasets.
    511              *
    512              * See data_stored_as_empty_string() and its related test.
    513              */
    514         );
    515     }
    516 
    517     /**
    518      * Data provider.
    519      *
    520      * @return array
    521      */
    522     public function data_strictly_equal_values() {
    523         $obj = new stdClass();
    524 
    525         return array(
    526             // Truthy values.
    527             '(string) "1"'       => array( '1', '1' ),
    528             '(int) 1'            => array( 1, 1 ),
    529             '(float) 1.0'        => array( 1.0, 1.0 ),
    530             'true'               => array( true, true ),
    531             'string with spaces' => array( ' ', ' ' ),
    532             'non-empty array'    => array( array( 'false' ), array( 'false' ) ),
    533             'object'             => array( $obj, $obj ),
    534 
    535             // Falsey values.
    536             '(string) "0"'       => array( '0', '0' ),
    537             'empty string'       => array( '', '' ),
    538             '(int) 0'            => array( 0, 0 ),
    539             '(float) 0.0'        => array( 0.0, 0.0 ),
    540             'empty array'        => array( array(), array() ),
    541 
    542             /*
    543              * false and null are not included in these datasets
    544              * because false is the default value, which triggers
    545              * a call to add_network_option().
    546              *
    547              * See data_stored_as_empty_string() and its related test.
    548              */
    549         );
    550     }
    551 
    552     /**
    553      * Tests that update_network_option() handles a null new value when the new value
    554      * is retrieved from the cache.
    555      *
    556      * On Single Site, this will result in no additional queries as
    557      * the option_value database field is not nullable.
    558      *
    559      * On Multisite, this will result in one additional query as
    560      * the meta_value database field is nullable.
    561      *
    562      * @ticket 59360
    563      *
    564      * @covers ::update_network_option
    565      */
    566     public function test_update_network_option_should_handle_a_null_new_value_from_cache() {
    567         add_network_option( null, 'foo', '' );
    568 
    569         $num_queries = get_num_queries();
    570 
    571         // Comparison will happen against value cached during add_option() above.
    572         $updated = update_network_option( null, 'foo', null );
    573 
    574         $expected_queries = is_multisite() ? 1 : 0;
    575         $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." );
    576 
    577         if ( is_multisite() ) {
    578             $this->assertTrue( $updated, 'update_network_option() should have returned true.' );
    579         } else {
    580             $this->assertFalse( $updated, 'update_network_option() should have returned false.' );
    581         }
    582     }
    583 
    584     /**
    585      * Tests that update_network_option() handles a null new value when the new value
    586      * is retrieved from the database.
    587      *
    588      * On Single Site, this will result in only 1 additional query as
    589      * the option_value database field is not nullable.
    590      *
    591      * On Multisite, this will result in two additional queries as
    592      * the meta_value database field is nullable.
    593      *
    594      * @ticket 59360
    595      *
    596      * @covers ::update_network_option
    597      */
    598     public function test_update_network_option_should_handle_a_null_new_value_from_db() {
    599         add_network_option( null, 'foo', '' );
    600 
    601         $num_queries = get_num_queries();
    602 
    603         // Delete cache.
    604         $network_cache_key = get_current_network_id() . ':foo';
    605         wp_cache_delete( $network_cache_key, 'site-options' );
    606         wp_cache_delete( 'alloptions', 'options' );
    607 
    608         $updated = update_network_option( null, 'foo', null );
    609 
    610         $expected_queries = is_multisite() ? 2 : 1;
    611         $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." );
    612 
    613         if ( is_multisite() ) {
    614             $this->assertTrue( $updated, 'update_network_option() should have returned true.' );
    615         } else {
    616             $this->assertFalse( $updated, 'update_network_option() should have returned false.' );
    617         }
    618     }
    619 
    620     /**
    621      * Tests that update_network_option() handles a null new value when the new value
    622      * is retrieved from a refreshed cache.
    623      *
    624      * On Single Site, this will result in no additional queries as
    625      * the option_value database field is not nullable.
    626      *
    627      * On Multisite, this will result in one additional query as
    628      * the meta_value database field is nullable.
    629      *
    630      * @ticket 59360
    631      *
    632      * @covers ::update_network_option
    633      */
    634     public function test_update_network_option_should_handle_a_null_new_value_from_refreshed_cache() {
    635         add_network_option( null, 'foo', '' );
    636 
    637         // Delete and refresh cache from DB.
    638         wp_cache_delete( 'alloptions', 'options' );
    639         wp_load_alloptions();
    640 
    641         $num_queries = get_num_queries();
    642         $updated     = update_network_option( null, 'foo', null );
    643 
    644         $expected_queries = is_multisite() ? 1 : 0;
    645         $this->assertSame( $expected_queries, get_num_queries() - $num_queries, "The number of queries should have increased by $expected_queries." );
    646 
    647         if ( is_multisite() ) {
    648             $this->assertTrue( $updated, 'update_network_option() should have returned true.' );
    649         } else {
    650             $this->assertFalse( $updated, 'update_network_option() should have returned false.' );
    651         }
    652     }
    653 
    654     /**
    655      * Tests that update_network_option() adds a non-existent option when the new value
    656      * is stored as an empty string and false is the default value for the option.
    657      *
    658      * @ticket 59360
    659      *
    660      * @dataProvider data_stored_as_empty_string
    661      *
    662      * @param mixed $new_value A value that casts to an empty string.
    663      */
    664     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 ) {
    665         global $wpdb;
    666 
    667         if ( is_multisite() ) {
    668             $this->markTestSkipped( 'This test should only run on Single Site.' );
    669         }
    670 
    671         $this->assertTrue( update_network_option( null, 'foo', $new_value ), 'update_network_option() should have returned true.' );
    672 
    673         $actual = $wpdb->get_row( "SELECT option_value FROM $wpdb->options WHERE option_name = 'foo' LIMIT 1" );
    674 
    675         $this->assertIsObject( $actual, 'The option was not added to the database.' );
    676         $this->assertObjectHasProperty( 'option_value', $actual, 'The "option_value" property was not included.' );
    677         $this->assertSame( '', $actual->option_value, 'The value was not stored as an empty string.' );
    678     }
    679 
    680     /**
    681      * Data provider.
    682      *
    683      * @return array[]
    684      */
    685     public function data_stored_as_empty_string() {
    686         return array(
    687             'empty string' => array( '' ),
    688             'null'         => array( null ),
    689         );
    690     }
    691 
    692     /**
    693      * Tests that a non-existent option is added even when its pre filter returns a value.
    694      *
    695      * @ticket 59360
    696      *
    697      * @covers ::update_network_option
    698      */
    699     public function test_update_network_option_with_pre_filter_adds_missing_option() {
    700         $hook_name = is_multisite() ? 'pre_site_option_foo' : 'pre_option_foo';
    701 
    702         // Force a return value of integer 0.
    703         add_filter( $hook_name, '__return_zero' );
    704 
    705         /*
    706          * This should succeed, since the 'foo' option does not exist in the database.
    707          * The default value is false, so it differs from 0.
    708          */
    709         $this->assertTrue( update_network_option( null, 'foo', 0 ) );
    710     }
    711 
    712     /**
    713      * Tests that an existing option is updated even when its pre filter returns the same value.
    714      *
    715      * @ticket 59360
    716      *
    717      * @covers ::update_network_option
    718      */
    719     public function test_update_network_option_with_pre_filter_updates_option_with_different_value() {
    720         $hook_name = is_multisite() ? 'pre_site_option_foo' : 'pre_option_foo';
    721 
    722         // Add the option with a value of 1 to the database.
    723         update_network_option( null, 'foo', 1 );
    724 
    725         // Force a return value of integer 0.
    726         add_filter( $hook_name, '__return_zero' );
    727 
    728         /*
    729          * This should succeed, since the 'foo' option has a value of 1 in the database.
    730          * Therefore it differs from 0 and should be updated.
    731          */
    732         $this->assertTrue( update_network_option( null, 'foo', 0 ) );
    733     }
    734 
    735     /**
    736      * Tests that calling update_network_option() does not permanently remove pre filters.
    737      *
    738      * @ticket 59360
    739      *
    740      * @covers ::update_network_option
    741      */
    742     public function test_update_network_option_maintains_pre_filters() {
    743         $hook_name = is_multisite() ? 'pre_site_option_foo' : 'pre_option_foo';
    744 
    745         add_filter( $hook_name, '__return_zero' );
    746         update_network_option( null, 'foo', 0 );
    747 
    748         // Assert that the filter is still present.
    749         $this->assertSame( 10, has_filter( $hook_name, '__return_zero' ) );
    750     }
    751 
    752     /**
    753      * Tests that update_network_option() conditionally applies
    754      * 'pre_site_option_{$option}' and 'pre_option_{$option}' filters.
    755      *
    756      * @ticket 59360
    757      *
    758      * @covers ::update_network_option
    759      */
    760     public function test_update_network_option_should_conditionally_apply_pre_site_option_and_pre_option_filters() {
    761         $option      = 'foo';
    762         $site_hook   = new MockAction();
    763         $option_hook = new MockAction();
    764 
    765         add_filter( "pre_site_option_{$option}", array( $site_hook, 'filter' ) );
    766         add_filter( "pre_option_{$option}", array( $option_hook, 'filter' ) );
    767 
    768         update_network_option( null, $option, 'false' );
    769 
    770         $this->assertSame( 1, $site_hook->get_call_count(), "'pre_site_option_{$option}' filters occurred an unexpected number of times." );
    771         $this->assertSame( is_multisite() ? 0 : 1, $option_hook->get_call_count(), "'pre_option_{$option}' filters occurred an unexpected number of times." );
    772     }
    773 
    774     /**
    775      * Tests that update_network_option() conditionally applies
    776      * 'default_site_{$option}' and 'default_option_{$option}' filters.
    777      *
    778      * @ticket 59360
    779      *
    780      * @covers ::update_network_option
    781      */
    782     public function test_update_network_option_should_conditionally_apply_site_and_option_default_value_filters() {
    783         $option      = 'foo';
    784         $site_hook   = new MockAction();
    785         $option_hook = new MockAction();
    786 
    787         add_filter( "default_site_option_{$option}", array( $site_hook, 'filter' ) );
    788         add_filter( "default_option_{$option}", array( $option_hook, 'filter' ) );
    789 
    790         update_network_option( null, $option, 'false' );
    791 
    792         $this->assertSame( 2, $site_hook->get_call_count(), "'default_site_option_{$option}' filters occurred an unexpected number of times." );
    793         $this->assertSame( is_multisite() ? 0 : 2, $option_hook->get_call_count(), "'default_option_{$option}' filters occurred an unexpected number of times." );
    794     }
    795 
    796     /**
    797      * Tests that update_network_option() adds a non-existent option that uses a filtered default value.
    798      *
    799      * @ticket 59360
    800      *
    801      * @covers ::update_network_option
    802      */
    803     public function test_update_network_option_should_add_option_with_filtered_default_value() {
    804         global $wpdb;
    805 
    806         $option               = 'foo';
    807         $default_site_value   = 'default-site-value';
    808         $default_option_value = 'default-option-value';
    809 
    810         add_filter(
    811             "default_site_option_{$option}",
    812             static function () use ( $default_site_value ) {
    813                 return $default_site_value;
    814             }
    815         );
    816 
    817         add_filter(
    818             "default_option_{$option}",
    819             static function () use ( $default_option_value ) {
    820                 return $default_option_value;
    821             }
    822         );
    823 
    824         /*
    825          * For a non existing option with the unfiltered default of false, passing false here wouldn't work.
    826          * Because the default is different than false here though, passing false is expected to result in
    827          * a database update.
    828          */
    829         $this->assertTrue( update_network_option( null, $option, false ), 'update_network_option() should have returned true.' );
    830 
    831         if ( is_multisite() ) {
    832             $actual = $wpdb->get_row(
    833                 $wpdb->prepare(
    834                     "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s LIMIT 1",
    835                     $option
    836                 )
    837             );
    838         } else {
    839             $actual = $wpdb->get_row(
    840                 $wpdb->prepare(
    841                     "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
    842                     $option
    843                 )
    844             );
    845         }
    846 
    847         $value_field = is_multisite() ? 'meta_value' : 'option_value';
    848 
    849         $this->assertIsObject( $actual, 'The option was not added to the database.' );
    850         $this->assertObjectHasProperty( $value_field, $actual, "The '$value_field' property was not included." );
    851         $this->assertSame( '', $actual->$value_field, 'The new value was not stored in the database.' );
    852     }
    853231}
  • trunk/tests/phpunit/tests/option/option.php

    r56796 r56946  
    371371
    372372    /**
    373      * Tests that update_option() triggers one additional query and returns true
    374      * for some loosely equal old and new values when the old value is retrieved from the cache.
    375      *
    376      * The additional query is triggered to update the value in the database.
    377      *
    378      * @ticket 22192
    379      *
    380      * @covers ::update_option
    381      *
    382      * @dataProvider data_loosely_equal_values_that_should_update
    383      *
    384      * @param mixed $old_value The old value.
    385      * @param mixed $new_value The new value to try to set.
    386      */
    387     public function test_update_option_should_update_some_loosely_equal_values_from_cache( $old_value, $new_value ) {
    388         add_option( 'foo', $old_value );
    389 
    390         $num_queries = get_num_queries();
    391 
    392         // Comparison will happen against value cached during add_option() above.
    393         $updated = update_option( 'foo', $new_value );
    394 
    395         $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run to update the value.' );
    396         $this->assertTrue( $updated, 'update_option() should have returned true.' );
    397     }
    398 
    399     /**
    400      * Tests that update_option() triggers two additional queries and returns true
    401      * for some loosely equal old and new values when the old value is retrieved from the database.
    402      *
    403      * The two additional queries are triggered to:
    404      * 1. retrieve the old value from the database, as the option does not exist in the cache.
    405      * 2. update the value in the database.
    406      *
    407      * @ticket 22192
    408      *
    409      * @covers ::update_option
    410      *
    411      * @dataProvider data_loosely_equal_values_that_should_update
    412      *
    413      * @param mixed $old_value The old value.
    414      * @param mixed $new_value The new value to try to set.
    415      */
    416     public function test_update_option_should_update_some_loosely_equal_values_from_db( $old_value, $new_value ) {
    417         add_option( 'foo', $old_value );
    418 
    419         $num_queries = get_num_queries();
    420 
    421         // Delete cache.
    422         wp_cache_delete( 'alloptions', 'options' );
    423         $updated = update_option( 'foo', $new_value );
    424 
    425         $this->assertSame( 2, get_num_queries() - $num_queries, 'Two additional queries should have run.' );
    426         $this->assertTrue( $updated, 'update_option() should have returned true.' );
    427     }
    428 
    429     /**
    430      * Tests that update_option() triggers one additional query and returns true
    431      * for some loosely equal old and new values when the old value is retrieved from a refreshed cache.
    432      *
    433      * The additional query is triggered to update the value in the database.
    434      *
    435      * @ticket 22192
    436      *
    437      * @covers ::update_option
    438      *
    439      * @dataProvider data_loosely_equal_values_that_should_update
    440      *
    441      * @param mixed $old_value The old value.
    442      * @param mixed $new_value The new value to try to set.
    443      */
    444     public function test_update_option_should_update_some_loosely_equal_values_from_refreshed_cache( $old_value, $new_value ) {
    445         add_option( 'foo', $old_value );
    446 
    447         // Delete and refresh cache from DB.
    448         wp_cache_delete( 'alloptions', 'options' );
    449         wp_load_alloptions();
    450 
    451         $num_queries = get_num_queries();
    452         $updated     = update_option( 'foo', $new_value );
    453 
    454         $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run to update the value.' );
    455         $this->assertTrue( $updated, 'update_option() should have returned true.' );
    456     }
    457 
    458     /**
    459      * Data provider.
    460      *
    461      * @return array
    462      */
    463     public function data_loosely_equal_values_that_should_update() {
    464         return array(
    465             // Falsey values.
    466             '(string) "0" to false'       => array( '0', false ),
    467             'empty string to (int) 0'     => array( '', 0 ),
    468             'empty string to (float) 0.0' => array( '', 0.0 ),
    469             '(int) 0 to empty string'     => array( 0, '' ),
    470             '(int) 0 to false'            => array( 0, false ),
    471             '(float) 0.0 to empty string' => array( 0.0, '' ),
    472             '(float) 0.0 to false'        => array( 0.0, false ),
    473             'false to (string) "0"'       => array( false, '0' ),
    474             'false to (int) 0'            => array( false, 0 ),
    475             'false to (float) 0.0'        => array( false, 0.0 ),
    476 
    477             // Non-scalar values.
    478             'false to array()'            => array( false, array() ),
    479             '(string) "false" to array()' => array( 'false', array() ),
    480             'empty string to array()'     => array( '', array() ),
    481             '(int 0) to array()'          => array( 0, array() ),
    482             '(string) "0" to array()'     => array( '0', array() ),
    483             '(string) "false" to null'    => array( 'false', null ),
    484             '(int) 0 to null'             => array( 0, null ),
    485             '(string) "0" to null'        => array( '0', null ),
    486             'array() to false'            => array( array(), false ),
    487             'array() to (string) "false"' => array( array(), 'false' ),
    488             'array() to empty string'     => array( array(), '' ),
    489             'array() to (int) 0'          => array( array(), 0 ),
    490             'array() to (string) "0"'     => array( array(), '0' ),
    491             'array() to null'             => array( array(), null ),
    492         );
    493     }
    494 
    495     /**
    496      * Tests that update_option() triggers no additional queries and returns false
    497      * for some values when the old value is retrieved from the cache.
    498      *
    499      * @ticket 22192
    500      *
    501      * @covers ::update_option
    502      *
    503      * @dataProvider data_loosely_equal_values_that_should_not_update
    504      * @dataProvider data_strictly_equal_values
    505      *
    506      * @param mixed $old_value The old value.
    507      * @param mixed $new_value The new value to try to set.
    508      */
    509     public function test_update_option_should_not_update_some_values_from_cache( $old_value, $new_value ) {
    510         add_option( 'foo', $old_value );
    511 
    512         $num_queries = get_num_queries();
    513 
    514         // Comparison will happen against value cached during add_option() above.
    515         $updated = update_option( 'foo', $new_value );
    516 
    517         $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' );
    518         $this->assertFalse( $updated, 'update_option() should have returned false.' );
    519     }
    520 
    521     /**
    522      * Tests that update_option() triggers one additional query and returns false
    523      * for some values when the old value is retrieved from the database.
    524      *
    525      * The additional query is triggered to retrieve the old value from the database.
    526      *
    527      * @ticket 22192
    528      *
    529      * @covers ::update_option
    530      *
    531      * @dataProvider data_loosely_equal_values_that_should_not_update
    532      * @dataProvider data_strictly_equal_values
    533      *
    534      * @param mixed $old_value The old value.
    535      * @param mixed $new_value The new value to try to set.
    536      */
    537     public function test_update_option_should_not_update_some_values_from_db( $old_value, $new_value ) {
    538         add_option( 'foo', $old_value );
    539 
    540         $num_queries = get_num_queries();
    541 
    542         // Delete cache.
    543         wp_cache_delete( 'alloptions', 'options' );
    544         $updated = update_option( 'foo', $new_value );
    545 
    546         $this->assertSame( 1, get_num_queries() - $num_queries, 'One additional query should have run.' );
    547         $this->assertFalse( $updated, 'update_option() should have returned false.' );
    548     }
    549 
    550     /**
    551      * Tests that update_option() triggers no additional queries and returns false
    552      * for some values when the old value is retrieved from a refreshed cache.
    553      *
    554      * @ticket 22192
    555      *
    556      * @covers ::update_option
    557      *
    558      * @dataProvider data_loosely_equal_values_that_should_not_update
    559      * @dataProvider data_strictly_equal_values
    560      *
    561      * @param mixed $old_value The old value.
    562      * @param mixed $new_value The new value to try to set.
    563      */
    564     public function test_update_option_should_not_update_some_values_from_refreshed_cache( $old_value, $new_value ) {
    565         add_option( 'foo', $old_value );
    566 
    567         // Delete and refresh cache from DB.
    568         wp_cache_delete( 'alloptions', 'options' );
    569         wp_load_alloptions();
    570 
    571         $num_queries = get_num_queries();
    572         $updated     = update_option( 'foo', $new_value );
    573 
    574         $this->assertSame( $num_queries, get_num_queries(), 'No additional queries should have run.' );
    575         $this->assertFalse( $updated, 'update_option() should have returned false.' );
    576     }
    577 
    578     /**
    579      * Data provider.
    580      *
    581      * @return array[]
    582      */
    583     public function data_loosely_equal_values_that_should_not_update() {
    584         return array(
    585             // Truthy values.
    586             '(string) "1" to (int) 1'     => array( '1', 1 ),
    587             '(string) "1" to (float) 1.0' => array( '1', 1.0 ),
    588             '(string) "1" to true'        => array( '1', true ),
    589             '(int) 1 to (string) "1"'     => array( 1, '1' ),
    590             '1 to (float) 1.0'            => array( 1, 1.0 ),
    591             '(int) 1 to true'             => array( 1, true ),
    592             '(float) 1.0 to (string) "1"' => array( 1.0, '1' ),
    593             '(float) 1.0 to (int) 1'      => array( 1.0, 1 ),
    594             '1.0 to true'                 => array( 1.0, true ),
    595             'true to (string) "1"'        => array( true, '1' ),
    596             'true to 1'                   => array( true, 1 ),
    597             'true to (float) 1.0'         => array( true, 1.0 ),
    598 
    599             // Falsey values.
    600             '(string) "0" to (int) 0'     => array( '0', 0 ),
    601             '(string) "0" to (float) 0.0' => array( '0', 0.0 ),
    602             '(int) 0 to (string) "0"'     => array( 0, '0' ),
    603             '(int) 0 to (float) 0.0'      => array( 0, 0.0 ),
    604             '(float) 0.0 to (string) "0"' => array( 0.0, '0' ),
    605             '(float) 0.0 to (int) 0'      => array( 0.0, 0 ),
    606             'empty string to false'       => array( '', false ),
    607             'empty string to null'        => array( '', null ),
    608 
    609             /*
    610              * null as an initial value behaves differently by triggering
    611              * a query, so it is not included in these datasets.
    612              *
    613              * See data_stored_as_empty_string() and its related test.
    614              */
    615         );
    616     }
    617 
    618     /**
    619      * Data provider.
    620      *
    621      * @return array
    622      */
    623     public function data_strictly_equal_values() {
    624         $obj = new stdClass();
    625 
    626         return array(
    627             // Truthy values.
    628             '(string) "1"'       => array( '1', '1' ),
    629             '(int) 1'            => array( 1, 1 ),
    630             '(float) 1.0'        => array( 1.0, 1.0 ),
    631             'true'               => array( true, true ),
    632             'string with spaces' => array( ' ', ' ' ),
    633             'non-empty array'    => array( array( 'false' ), array( 'false' ) ),
    634             'object'             => array( $obj, $obj ),
    635 
    636             // Falsey values.
    637             '(string) "0"'       => array( '0', '0' ),
    638             'empty string'       => array( '', '' ),
    639             '(int) 0'            => array( 0, 0 ),
    640             '(float) 0.0'        => array( 0.0, 0.0 ),
    641             'empty array'        => array( array(), array() ),
    642             'false'              => array( false, false ),
    643 
    644             /*
    645              * null is not included in these datasets because
    646              * false is the default value, which triggers
    647              * a call to add_option().
    648              *
    649              * See data_stored_as_empty_string() and its related test.
    650              */
    651         );
    652     }
    653 
    654     /**
    655      * Tests that update_option() adds a non-existent option when the new value
    656      * is stored as an empty string and false is the default value for the option.
    657      *
    658      * @ticket 22192
    659      *
    660      * @dataProvider data_stored_as_empty_string
    661      *
    662      * @param mixed $new_value A value that casts to an empty string.
    663      */
    664     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 ) {
    665         global $wpdb;
    666 
    667         $this->assertTrue( update_option( 'foo', $new_value ), 'update_option() should have returned true.' );
    668 
    669         $actual = $wpdb->get_row( "SELECT option_value FROM $wpdb->options WHERE option_name = 'foo' LIMIT 1" );
    670 
    671         $this->assertIsObject( $actual, 'The option was not added to the database.' );
    672         $this->assertObjectHasProperty( 'option_value', $actual, 'The "option_value" property was not included.' );
    673         $this->assertSame( '', $actual->option_value, 'The value was not stored as an empty string.' );
    674     }
    675 
    676     /**
    677      * Data provider.
    678      *
    679      * @return array[]
    680      */
    681     public function data_stored_as_empty_string() {
    682         return array(
    683             'empty string' => array( '' ),
    684             'null'         => array( null ),
    685         );
    686     }
    687 
    688     /**
    689      * Tests that update_option() adds a non-existent option that uses a filtered default value.
    690      *
    691      * @ticket 22192
    692      *
    693      * @covers ::update_option
    694      */
    695     public function test_update_option_should_add_option_with_filtered_default_value() {
    696         global $wpdb;
    697 
    698         $option        = 'update_option_custom_default';
    699         $default_value = 'default-value';
    700 
    701         add_filter(
    702             "default_option_{$option}",
    703             static function () use ( $default_value ) {
    704                 return $default_value;
    705             }
    706         );
    707 
    708         /*
    709          * For a non existing option with the unfiltered default of false, passing false here wouldn't work.
    710          * Because the default is different than false here though, passing false is expected to result in
    711          * a database update.
    712          */
    713         $this->assertTrue( update_option( $option, false ), 'update_option() should have returned true.' );
    714 
    715         $actual = $wpdb->get_row(
    716             $wpdb->prepare(
    717                 "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1",
    718                 $option
    719             )
    720         );
    721 
    722         $this->assertIsObject( $actual, 'The option was not added to the database.' );
    723         $this->assertObjectHasProperty( 'option_value', $actual, 'The "option_value" property was not included.' );
    724         $this->assertSame( '', $actual->option_value, 'The new value was not stored in the database.' );
    725     }
    726 
    727     /**
    728      * Tests that a non-existent option is added even when its pre filter returns a value.
    729      *
    730      * @ticket 22192
    731      *
    732      * @covers ::update_option
    733      */
    734     public function test_update_option_with_pre_filter_adds_missing_option() {
    735         // Force a return value of integer 0.
    736         add_filter( 'pre_option_foo', '__return_zero' );
    737 
    738         /*
    739          * This should succeed, since the 'foo' option does not exist in the database.
    740          * The default value is false, so it differs from 0.
    741          */
    742         $this->assertTrue( update_option( 'foo', 0 ) );
    743     }
    744 
    745     /**
    746      * Tests that an existing option is updated even when its pre filter returns the same value.
    747      *
    748      * @ticket 22192
    749      *
    750      * @covers ::update_option
    751      */
    752     public function test_update_option_with_pre_filter_updates_option_with_different_value() {
    753         // Add the option with a value of 1 to the database.
    754         add_option( 'foo', 1 );
    755 
    756         // Force a return value of integer 0.
    757         add_filter( 'pre_option_foo', '__return_zero' );
    758 
    759         /*
    760          * This should succeed, since the 'foo' option has a value of 1 in the database.
    761          * Therefore it differs from 0 and should be updated.
    762          */
    763         $this->assertTrue( update_option( 'foo', 0 ) );
    764     }
    765 
    766     /**
    767      * Tests that calling update_option() does not permanently remove pre filters.
    768      *
    769      * @ticket 22192
    770      *
    771      * @covers ::update_option
    772      */
    773     public function test_update_option_maintains_pre_filters() {
    774         add_filter( 'pre_option_foo', '__return_zero' );
    775         update_option( 'foo', 0 );
    776 
    777         // Assert that the filter is still present.
    778         $this->assertSame( 10, has_filter( 'pre_option_foo', '__return_zero' ) );
    779     }
    780 
    781     /**
    782373     * Tests that calling update_option() with changed autoload from 'no' to 'yes' updates the cache correctly.
    783374     *
Note: See TracChangeset for help on using the changeset viewer.