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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/wp-includes/option.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,22 @@ function get_option( $option, $default_value = false ) {
$value = untrailingslashit( $value );
}

$raw_value = $value;
$value = maybe_unserialize( $value );

/*
* If the stored value was serialized but could not be unserialized, `maybe_unserialize()`
* returns `false`. In that case, return the default value to avoid incorrectly returning
* `false` instead of the caller's expected default.
*
* Note: `b:0;` is the valid serialization of boolean `false`. When properly unserialized
* it returns `false`, which is correct and should not be treated as a failure.
*/
if ( false === $value && is_serialized( $raw_value ) && 'b:0;' !== trim( $raw_value ) ) {
/** This filter is documented in wp-includes/option.php */
return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
}

/**
* Filters the value of an existing option.
*
Expand All @@ -253,7 +269,7 @@ function get_option( $option, $default_value = false ) {
* unserialized prior to being returned.
* @param string $option Option name.
*/
return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option );
return apply_filters( "option_{$option}", $value, $option );
}

/**
Expand Down
69 changes: 69 additions & 0 deletions tests/phpunit/tests/option/option.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,75 @@ public function test_serialized_data() {
$this->assertTrue( delete_option( $key ) );
}

/**
* Tests that get_option() returns the default value when stored data is corrupted/unserializable.
*
* @ticket 59588
*
* @covers ::get_option
*/
public function test_get_option_returns_default_when_unserialize_fails() {
global $wpdb;

// Insert a corrupted serialized value directly into the DB.
$option_name = 'test_corrupted_option_59588';
$wpdb->insert(
$wpdb->options,
array(
'option_name' => $option_name,
'option_value' => 'a:2:{i:0;s:3:"foo";', // Truncated/corrupted serialized array.
'autoload' => 'yes',
)
);
// Clear alloptions cache so get_option() reads from DB.
wp_cache_delete( 'alloptions', 'options' );
wp_cache_delete( $option_name, 'options' );

$default = array( 'default_key' => 'default_value' );
$result = get_option( $option_name, $default );

// Cleanup.
$wpdb->delete( $wpdb->options, array( 'option_name' => $option_name ) );
wp_cache_delete( 'alloptions', 'options' );
wp_cache_delete( $option_name, 'options' );

$this->assertSame( $default, $result, 'get_option() should return the default value when stored data cannot be unserialized.' );
}

/**
* Tests that get_option() correctly returns false when the stored value is a serialized boolean false.
*
* @ticket 59588
*
* @covers ::get_option
*/
public function test_get_option_returns_false_for_serialized_false_value() {
global $wpdb;

// Insert the serialization of boolean false (`b:0;`) directly into the DB.
$option_name = 'test_serialized_false_59588';
$wpdb->insert(
$wpdb->options,
array(
'option_name' => $option_name,
'option_value' => 'b:0;',
'autoload' => 'yes',
)
);
// Clear alloptions cache so get_option() reads from DB.
wp_cache_delete( 'alloptions', 'options' );
wp_cache_delete( $option_name, 'options' );

$result = get_option( $option_name, 'should-not-be-returned' );

// Cleanup.
$wpdb->delete( $wpdb->options, array( 'option_name' => $option_name ) );
wp_cache_delete( 'alloptions', 'options' );
wp_cache_delete( $option_name, 'options' );

$this->assertFalse( $result, 'get_option() should return false when the stored value is a serialized boolean false.' );
}

/**
* @ticket 23289
*
Expand Down
Loading