diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 93f70f7b50..fc2f8ddce1 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -364,6 +364,172 @@ function get_options( $options ) { return $result; } +/** + * Sets the autoload values for multiple options in the database. + * + * Autoloading too many options can lead to performance problems, especially if the options are not frequently used. + * This function allows modifying the autoload value for multiple options without changing the actual option value. + * This is for example recommended for plugin activation and deactivation hooks, to ensure any options exclusively used + * by the plugin which are generally autoloaded can be set to not autoload when the plugin is inactive. + * + * @since 6.4.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param array $options Associative array of option names and their autoload values to set. The option names are + * expected to not be SQL-escaped. The autoload values accept 'yes'|true to enable or 'no'|false + * to disable. + * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value + * was updated. + */ +function wp_set_option_autoload_values( array $options ) { + global $wpdb; + + if ( ! $options ) { + return array(); + } + + $grouped_options = array( + 'yes' => array(), + 'no' => array(), + ); + $results = array(); + foreach ( $options as $option => $autoload ) { + wp_protect_special_option( $option ); // Ensure only valid options can be passed. + if ( 'no' === $autoload || false === $autoload ) { // Sanitize autoload value and categorize accordingly. + $grouped_options['no'][] = $option; + } else { + $grouped_options['yes'][] = $option; + } + $results[ $option ] = false; // Initialize result value. + } + + $where = array(); + $where_args = array(); + foreach ( $grouped_options as $autoload => $options ) { + if ( ! $options ) { + continue; + } + $placeholders = implode( ',', array_fill( 0, count( $options ), '%s' ) ); + $where[] = "autoload != '%s' AND option_name IN ($placeholders)"; + $where_args[] = $autoload; + foreach ( $options as $option ) { + $where_args[] = $option; + } + } + $where = 'WHERE ' . implode( ' OR ', $where ); + + /* + * Determine the relevant options that do not already use the given autoload value. + * If no options are returned, no need to update. + */ + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $options_to_update = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM $wpdb->options $where", $where_args ) ); + if ( ! $options_to_update ) { + return $results; + } + + // Run UPDATE queries as needed (maximum 2) to update the relevant options' autoload values to 'yes' or 'no'. + foreach ( $grouped_options as $autoload => $options ) { + if ( ! $options ) { + continue; + } + $options = array_intersect( $options, $options_to_update ); + $grouped_options[ $autoload ] = $options; + if ( ! $grouped_options[ $autoload ] ) { + continue; + } + + // Run query to update autoload value for all the options where it is needed. + $success = $wpdb->query( + $wpdb->prepare( + "UPDATE $wpdb->options SET autoload = %s WHERE option_name IN (" . implode( ',', array_fill( 0, count( $grouped_options[ $autoload ] ), '%s' ) ) . ')', + array_merge( + array( $autoload ), + $grouped_options[ $autoload ] + ) + ) + ); + if ( ! $success ) { + // Set option list to an empty array to indicate no options were updated. + $grouped_options[ $autoload ] = array(); + continue; + } + + // Assume that on success all options were updated, which should be the case given only new values are sent. + foreach ( $grouped_options[ $autoload ] as $option ) { + $results[ $option ] = true; + } + } + + /* + * If any options were changed to 'yes', delete their individual caches, and delete 'alloptions' cache so that it + * is refreshed as needed. + * If no options were changed to 'yes' but any options were changed to 'no', delete them from the 'alloptions' + * cache. This is not necessary when options were changed to 'yes', since in that situation the entire cache is + * deleted anyway. + */ + if ( $grouped_options['yes'] ) { + wp_cache_delete_multiple( $grouped_options['yes'], 'options' ); + wp_cache_delete( 'alloptions', 'options' ); + } elseif ( $grouped_options['no'] ) { + $alloptions = wp_load_alloptions( true ); + foreach ( $grouped_options['no'] as $option ) { + if ( isset( $alloptions[ $option ] ) ) { + unset( $alloptions[ $option ] ); + } + } + wp_cache_set( 'alloptions', $alloptions, 'options' ); + } + + return $results; +} + +/** + * Sets the autoload value for multiple options in the database. + * + * This is a wrapper for {@see wp_set_option_autoload_values()}, which can be used to set different autoload values for + * each option at once. + * + * @since 6.4.0 + * + * @see wp_set_option_autoload_values() + * + * @param array $options List of option names. Expected to not be SQL-escaped. + * @param string|bool $autoload Autoload value to control whether to load the options when WordPress starts up. + * Accepts 'yes'|true to enable or 'no'|false to disable. + * @return array Associative array of all provided $options as keys and boolean values for whether their autoload value + * was updated. + */ +function wp_set_options_autoload( array $options, $autoload ) { + return wp_set_option_autoload_values( + array_fill_keys( $options, $autoload ) + ); +} + +/** + * Sets the autoload value for an option in the database. + * + * This is a wrapper for {@see wp_set_option_autoload_values()}, which can be used to set the autoload value for + * multiple options at once. + * + * @since 6.4.0 + * + * @see wp_set_option_autoload_values() + * + * @param string $option Name of the option. Expected to not be SQL-escaped. + * @param string|bool $autoload Autoload value to control whether to load the option when WordPress starts up. + * Accepts 'yes'|true to enable or 'no'|false to disable. + * @return bool True if the autoload value was modified, false otherwise. + */ +function wp_set_option_autoload( $option, $autoload ) { + $result = wp_set_option_autoload_values( array( $option => $autoload ) ); + if ( isset( $result[ $option ] ) ) { + return $result[ $option ]; + } + return false; +} + /** * Protects WordPress special option from being modified. * diff --git a/tests/phpunit/tests/option/wpSetOptionAutoload.php b/tests/phpunit/tests/option/wpSetOptionAutoload.php new file mode 100644 index 0000000000..8736005e32 --- /dev/null +++ b/tests/phpunit/tests/option/wpSetOptionAutoload.php @@ -0,0 +1,114 @@ +assertTrue( wp_set_option_autoload( $option, 'yes' ), 'Function did not succeed' ); + $this->assertSame( 'yes', $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Option autoload value not updated in database' ); + $this->assertFalse( wp_cache_get( $option, 'options' ), 'Option not deleted from individual cache' ); + $this->assertFalse( wp_cache_get( 'alloptions', 'options' ), 'Alloptions cache not cleared' ); + } + + /** + * Tests that setting an option's autoload value to 'no' works as expected. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_no() { + global $wpdb; + + $option = 'test_option'; + $value = 'value'; + + add_option( $option, $value, '', 'yes' ); + + $this->assertTrue( wp_set_option_autoload( $option, 'no' ), 'Function did not succeed' ); + $this->assertSame( 'no', $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Option autoload value not updated in database' ); + $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), 'Option not deleted from alloptions cache' ); + } + + /** + * Tests that setting an option's autoload value to the same value as prior works as expected. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_same() { + global $wpdb; + + $option = 'test_option'; + $value = 'value'; + + add_option( $option, $value, '', 'yes' ); + + $this->assertFalse( wp_set_option_autoload( $option, 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertSame( 'yes', $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Option autoload value unexpectedly updated in database' ); + } + + /** + * Tests that setting a missing option's autoload value does not do anything. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_missing() { + global $wpdb; + + $option = 'test_option'; + + $this->assertFalse( wp_set_option_autoload( $option, 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertNull( $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Missing option autoload value was set in database' ); + $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), 'Missing option found in alloptions cache' ); + $this->assertFalse( wp_cache_get( $option, 'options' ), 'Missing option found in individual cache' ); + } + + /** + * Tests setting an option's autoload value to boolean true. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_true() { + global $wpdb; + + $option = 'test_option'; + $value = 'value'; + + add_option( $option, $value, '', false ); + + $this->assertTrue( wp_set_option_autoload( $option, true ), 'Function did not succeed' ); + $this->assertSame( 'yes', $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Option autoload value not updated in database' ); + } + + /** + * Tests setting an option's autoload value to boolean false. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_false() { + global $wpdb; + + $option = 'test_option'; + $value = 'value'; + + add_option( $option, $value, '', true ); + + $this->assertTrue( wp_set_option_autoload( $option, false ), 'Function did not succeed' ); + $this->assertSame( 'no', $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s", $option ) ), 'Option autoload value not updated in database' ); + } +} diff --git a/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php b/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php new file mode 100644 index 0000000000..a49348db13 --- /dev/null +++ b/tests/phpunit/tests/option/wpSetOptionAutoloadValues.php @@ -0,0 +1,240 @@ + 'yes', + 'test_option2' => 'yes', + ); + add_option( 'test_option1', 'value1', '', 'yes' ); + add_option( 'test_option2', 'value2', '', 'no' ); + $expected = array( + 'test_option1' => false, + 'test_option2' => true, + ); + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries + 2, get_num_queries(), 'Function made unexpected amount of database queries' ); + $this->assertSame( array( 'yes', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $autoload ) { + $this->assertFalse( wp_cache_get( $option, 'options' ), sprintf( 'Option %s not deleted from individual cache', $option ) ); + } + $this->assertFalse( wp_cache_get( 'alloptions', 'options' ), 'Alloptions cache not cleared' ); + } + + /** + * Tests setting options' autoload to 'no' where for some options this is already the case. + * + * In this case, the 'alloptions' cache should not be cleared, but only its options set to 'no' should be deleted. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_all_no_partial_update() { + global $wpdb; + + $options = array( + 'test_option1' => 'no', + 'test_option2' => 'no', + ); + add_option( 'test_option1', 'value1', '', 'yes' ); + add_option( 'test_option2', 'value2', '', 'no' ); + $expected = array( + 'test_option1' => true, + 'test_option2' => false, + ); + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries + 2, get_num_queries(), 'Function made unexpected amount of database queries' ); + $this->assertSame( array( 'no', 'no' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $autoload ) { + $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s not deleted from alloptions cache', $option ) ); + } + } + + /** + * Tests setting options' autoload to 'yes' where for all of them this is already the case. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_all_yes_no_update() { + global $wpdb; + + $options = array( + 'test_option1' => 'yes', + 'test_option2' => 'yes', + ); + add_option( 'test_option1', 'value1', '', 'yes' ); + add_option( 'test_option2', 'value2', '', 'yes' ); + $expected = array( + 'test_option1' => false, + 'test_option2' => false, + ); + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries + 1, get_num_queries(), 'Function made unexpected amount of database queries' ); + $this->assertSame( array( 'yes', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $autoload ) { + $this->assertArrayHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s unexpectedly deleted from alloptions cache', $option ) ); + } + } + + /** + * Tests setting options' autoload to either 'yes' or 'no' where for some options this is already the case. + * + * The test also covers one option that is entirely missing. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_mixed_partial_update() { + global $wpdb; + + $options = array( + 'test_option1' => 'yes', + 'test_option2' => 'no', + 'test_option3' => 'yes', + 'missing_opt' => 'yes', + ); + add_option( 'test_option1', 'value1', '', 'no' ); + add_option( 'test_option2', 'value2', '', 'yes' ); + add_option( 'test_option3', 'value3', '', 'yes' ); + $expected = array( + 'test_option1' => true, + 'test_option2' => true, + 'test_option3' => false, + 'missing_opt' => false, + ); + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries + 3, get_num_queries(), 'Function made unexpected amount of database queries' ); + $this->assertSameSets( array( 'yes', 'no', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $autoload ) { + $this->assertFalse( wp_cache_get( $option, 'options' ), sprintf( 'Option %s not deleted from individual cache', $option ) ); + } + $this->assertFalse( wp_cache_get( 'alloptions', 'options' ), 'Alloptions cache not cleared' ); + } + + /** + * Tests setting options' autoload to either 'yes' or 'no' while only the 'no' options actually need to be updated. + * + * In this case, the 'alloptions' cache should not be cleared, but only its options set to 'no' should be deleted. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_mixed_only_update_no() { + global $wpdb; + + $options = array( + 'test_option1' => 'yes', + 'test_option2' => 'no', + 'test_option3' => 'yes', + ); + add_option( 'test_option1', 'value1', '', 'yes' ); + add_option( 'test_option2', 'value2', '', 'yes' ); + add_option( 'test_option3', 'value3', '', 'yes' ); + $expected = array( + 'test_option1' => false, + 'test_option2' => true, + 'test_option3' => false, + ); + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries + 2, get_num_queries(), 'Function made unexpected amount of database queries' ); + $this->assertSameSets( array( 'yes', 'no', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $autoload ) { + if ( 'no' === $autoload ) { + $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s not deleted from alloptions cache', $option ) ); + } else { + $this->assertArrayHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s unexpectedly deleted from alloptions cache', $option ) ); + } + } + } + + /** + * Tests setting options' autoload with a simulated SQL query failure. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_with_sql_query_failure() { + global $wpdb; + + $options = array( + 'test_option1' => 'yes', + 'test_option2' => 'yes', + ); + add_option( 'test_option1', 'value1', '', 'no' ); + add_option( 'test_option2', 'value2', '', 'no' ); + + // Force UPDATE queries to fail, leading to no autoload values being updated. + add_filter( + 'query', + static function( $query ) { + if ( str_starts_with( $query, 'UPDATE ' ) ) { + return ''; + } + return $query; + } + ); + $expected = array( + 'test_option1' => false, + 'test_option2' => false, + ); + + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( array( 'no', 'no' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + } + + /** + * Tests setting options' autoload with boolean values. + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_with_bool() { + global $wpdb; + + $options = array( + 'test_option1' => true, + 'test_option2' => false, + ); + add_option( 'test_option1', 'value1', '', false ); + add_option( 'test_option2', 'value2', '', true ); + $expected = array( + 'test_option1' => true, + 'test_option2' => true, + ); + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_option_autoload_values( $options ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries + 3, get_num_queries(), 'Function made unexpected amount of database queries' ); + $this->assertSameSets( array( 'yes', 'no' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + } + + /** + * Tests calling the function with an empty array (i.e. do nothing). + * + * @ticket 58964 + */ + public function test_wp_set_option_autoload_values_with_empty_array() { + $num_queries = get_num_queries(); + $this->assertSame( array(), wp_set_option_autoload_values( array() ), 'Function produced unexpected result' ); + $this->assertSame( $num_queries, get_num_queries(), 'Function made unexpected amount of database queries' ); + } +} diff --git a/tests/phpunit/tests/option/wpSetOptionsAutoload.php b/tests/phpunit/tests/option/wpSetOptionsAutoload.php new file mode 100644 index 0000000000..5202283efa --- /dev/null +++ b/tests/phpunit/tests/option/wpSetOptionsAutoload.php @@ -0,0 +1,190 @@ + 'value1', + 'test_option2' => 'value2', + ); + + $expected = array(); + foreach ( $options as $option => $value ) { + add_option( $option, $value, '', 'no' ); + $expected[ $option ] = true; + } + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), 'yes' ), 'Function did not succeed' ); + $this->assertSame( $num_queries + 2, get_num_queries(), 'Updating options autoload value ran too many queries' ); + $this->assertSame( array( 'yes', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $value ) { + $this->assertFalse( wp_cache_get( $option, 'options' ), sprintf( 'Option %s not deleted from individual cache', $option ) ); + } + $this->assertFalse( wp_cache_get( 'alloptions', 'options' ), 'Alloptions cache not cleared' ); + } + + /** + * Tests that setting options' autoload value to 'no' works as expected. + * + * @ticket 58964 + */ + public function test_wp_set_options_autoload_no() { + global $wpdb; + + $options = array( + 'test_option1' => 'value1', + 'test_option2' => 'value2', + ); + + $expected = array(); + foreach ( $options as $option => $value ) { + add_option( $option, $value, '', 'yes' ); + $expected[ $option ] = true; + } + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), 'no' ), 'Function did not succeed' ); + $this->assertSame( $num_queries + 2, get_num_queries(), 'Updating options autoload value ran too many queries' ); + $this->assertSame( array( 'no', 'no' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $value ) { + $this->assertArrayNotHasKey( $option, wp_cache_get( 'alloptions', 'options' ), sprintf( 'Option %s not deleted from alloptions cache', $option ) ); + } + } + + /** + * Tests that setting options' autoload value to the same value as prior works as expected. + * + * @ticket 58964 + */ + public function test_wp_set_options_autoload_same() { + global $wpdb; + + $options = array( + 'test_option1' => 'value1', + 'test_option2' => 'value2', + ); + + $expected = array(); + foreach ( $options as $option => $value ) { + add_option( $option, $value, '', 'yes' ); + $expected[ $option ] = false; + } + + $num_queries = get_num_queries(); + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertSame( $num_queries + 1, get_num_queries(), 'Function attempted to update options autoload value in database' ); + $this->assertSame( array( 'yes', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Options autoload value unexpectedly updated in database' ); + } + + /** + * Tests that setting missing option's autoload value does not do anything. + * + * @ticket 58964 + */ + public function test_wp_set_options_autoload_missing() { + global $wpdb; + + $options = array( + 'test_option1', + 'test_option2', + ); + + $expected = array(); + foreach ( $options as $option ) { + $expected[ $option ] = false; + } + + $this->assertSame( $expected, wp_set_options_autoload( $options, 'yes' ), 'Function did unexpectedly succeed' ); + $this->assertSame( array(), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Missing options autoload value was set in database' ); + } + + /** + * Tests that setting option's autoload value only updates those that need to be updated. + * + * @ticket 58964 + */ + public function test_wp_set_options_autoload_mixed() { + global $wpdb; + + $options = array( + 'test_option1' => 'value1', + 'test_option2' => 'value2', + ); + + add_option( 'test_option1', $options['test_option1'], '', 'yes' ); + add_option( 'test_option2', $options['test_option2'], '', 'no' ); + $expected = array( + 'test_option1' => false, + 'test_option2' => true, + ); + + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), 'yes' ), 'Function produced unexpected result' ); + $this->assertSame( array( 'yes', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + foreach ( $options as $option => $value ) { + $this->assertFalse( wp_cache_get( $option, 'options' ), sprintf( 'Option %s not deleted from individual cache', $option ) ); + } + $this->assertFalse( wp_cache_get( 'alloptions', 'options' ), 'Alloptions cache not cleared' ); + } + + /** + * Tests setting option's autoload value with boolean true. + * + * @ticket 58964 + */ + public function test_wp_set_options_autoload_true() { + global $wpdb; + + $options = array( + 'test_option1' => 'value1', + 'test_option2' => 'value2', + ); + + add_option( 'test_option1', $options['test_option1'], '', false ); + add_option( 'test_option2', $options['test_option2'], '', false ); + $expected = array( + 'test_option1' => true, + 'test_option2' => true, + ); + + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), true ), 'Function produced unexpected result' ); + $this->assertSame( array( 'yes', 'yes' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + } + + /** + * Tests setting option's autoload value with boolean false. + * + * @ticket 58964 + */ + public function test_wp_set_options_autoload_false() { + global $wpdb; + + $options = array( + 'test_option1' => 'value1', + 'test_option2' => 'value2', + ); + + add_option( 'test_option1', $options['test_option1'], '', true ); + add_option( 'test_option2', $options['test_option2'], '', true ); + $expected = array( + 'test_option1' => true, + 'test_option2' => true, + ); + + $this->assertSame( $expected, wp_set_options_autoload( array_keys( $options ), false ), 'Function produced unexpected result' ); + $this->assertSame( array( 'no', 'no' ), $wpdb->get_col( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name IN (" . implode( ',', array_fill( 0, count( $options ), '%s' ) ) . ')', ...array_keys( $options ) ) ), 'Option autoload values not updated in database' ); + } +}