Options, Meta APIs: Introduce wp_set_option_autoload_values().

This function accepts an associative array of option names and their autoload values to set, and it will update those values in the database in bulk, only for those options where the autoload field is not already set to the given value.

Two wrapper functions for ease of use accompany the new main function:
* `wp_set_options_autoload( $options, $autoload )` can be used to set multiple options to the same autoload value.
* `wp_set_option_autoload( $option, $autoload )` can be used to set the autoload value for a single option.

All of these functions allow changing the autoload value of an option, which previously has only been possible in combination with updating the value. This limitation prevented some relevant use-cases: For example, a plugin deactivation hook could set all of its options to not autoload, as a cleanup routine, while not actually deleting any data. The plugin's activation hook could then implement the reverse, resetting those options' autoload values to the originally intended ones for when using the plugin.

Props boonebgorges, joemcgill, costdev, mukesh27, SergeyBiryukov, tabrisrp, flixos90.
Fixes #58964.


git-svn-id: https://develop.svn.wordpress.org/trunk@56508 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Felix Arntz
2023-09-01 19:55:11 +00:00
parent 5269bf66f4
commit a22fee0e5a
4 changed files with 710 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
<?php
/**
* Test wp_set_option_autoload().
*
* @group option
*
* @covers ::wp_set_option_autoload
*/
class Tests_Option_WpSetOptionAutoload extends WP_UnitTestCase {
/**
* Tests that setting an option's autoload value to 'yes' works as expected.
*
* @ticket 58964
*/
public function test_wp_set_option_autoload_yes() {
global $wpdb;
$option = 'test_option';
$value = 'value';
add_option( $option, $value, '', 'no' );
$this->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' );
}
}

View File

@@ -0,0 +1,240 @@
<?php
/**
* Test wp_set_option_autoload_values().
*
* @group option
*
* @covers ::wp_set_option_autoload_values
*/
class Tests_Option_WpSetOptionAutoloadValues extends WP_UnitTestCase {
/**
* Tests setting options' autoload to 'yes' where for some options this is already the case.
*
* @ticket 58964
*/
public function test_wp_set_option_autoload_values_all_yes_partial_update() {
global $wpdb;
$options = array(
'test_option1' => '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' );
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* Test wp_set_options_autoload().
*
* @group option
*
* @covers ::wp_set_options_autoload
*/
class Tests_Option_WpSetOptionsAutoload extends WP_UnitTestCase {
/**
* Tests that setting options' autoload value to 'yes' works as expected.
*
* @ticket 58964
*/
public function test_wp_set_options_autoload_yes() {
global $wpdb;
$options = array(
'test_option1' => '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' );
}
}