hardening 2.2: 2meet-data-optimizer-hivepress-addon 0.3.0 (transients in the object cache) #3

Merged
wpdev merged 6 commits from hardening/2.2 into main 2026-09-27 10:56:53 +08:00
3 changed files with 21 additions and 11 deletions
Showing only changes of commit 788ddccfb3 - Show all commits
+6 -4
View File
@@ -20,10 +20,12 @@ Released with 2meet-data-optimizer 1.2.0 (hardening 2.2).
- With a persistent object cache (`wp_using_ext_object_cache()`), the
transient filter keeps HivePress's per-post transient pairs in the
`tmdo_hp_transient` cache group instead of `wp_options`, where they piled
up (1.15 M pairs on dev36). Reads fall back to `wp_options` for values
written before; deletes clear both. Without an object cache nothing changes.
After upgrading, remove the rows left behind with core's
`wp tmdo cleanup-hp-transients --options --confirm`.
up (1.15 M pairs on dev36). The cache is then the only store: rows written
before are neither read nor deleted (HivePress rebuilds those caches once),
because a `get_option()` miss adds the name to WordPress's `notoptions` for
good and a hit keeps the old row in the `options` cache group with no expiry.
Without an object cache nothing changes. After upgrading, remove the rows
left behind with core's `wp tmdo cleanup-hp-transients --options --confirm`.
### Fixed
@@ -167,9 +167,14 @@ final class TMDO_Hivepress_Transient_Filter {
// HivePress cache layer reads the timeout *first*, then decides whether
// to read the value. We must preserve raw stored values until HivePress
// itself orders deletion.
$value = wp_using_ext_object_cache() ? wp_cache_get( $option_name, self::CACHE_GROUP ) : false;
if ( false === $value ) {
// wp_options: where values went before an object cache was used.
if ( wp_using_ext_object_cache() ) {
// The object cache is the only store. Rows left in wp_options from
// before are not read (`wp tmdo cleanup-hp-transients --options`
// removes them): a get_option() miss would add the name to
// `notoptions` for good, as later writes never reach wp_options.
$value = wp_cache_get( $option_name, self::CACHE_GROUP );
$value = false === $value ? null : $value;
} else {
$value = get_option( $option_name, null );
}
@@ -248,6 +253,7 @@ final class TMDO_Hivepress_Transient_Filter {
if ( wp_using_ext_object_cache() ) {
wp_cache_delete( $option_name, self::CACHE_GROUP );
return true;
}
delete_option( $option_name );
return true;
@@ -213,16 +213,18 @@ class HivepressTransientFilterTest extends TestCase {
$this->assertSame( array( '99999999' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_transient_timeout_hp_models/cat/v1', true ) );
}
public function test_with_an_object_cache_a_value_left_in_wp_options_still_reads(): void {
public function test_with_an_object_cache_a_value_left_in_wp_options_is_not_read(): void {
// A miss must not fall back to get_option(): the option is absent after
// cleanup and WordPress would add its name to `notoptions` for good.
$key = '_transient_hp_models/cat/v1';
$translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key );
update_option( '_transient_' . $translated, 'from-before' );
$GLOBALS['_wp_using_ext_object_cache'] = true;
$this->assertSame( array( 'from-before' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) );
$this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) );
}
public function test_with_an_object_cache_delete_clears_both_stores(): void {
public function test_with_an_object_cache_delete_leaves_wp_options_alone(): void {
$key = '_transient_hp_models/cat/v1';
$translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key );
update_option( '_transient_' . $translated, 'from-before' );
@@ -232,7 +234,7 @@ class HivepressTransientFilterTest extends TestCase {
WPDO_Hivepress_Transient_Filter::on_delete( null, 100, $key, '', false );
$this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) );
$this->assertFalse( get_option( '_transient_' . $translated ) );
$this->assertSame( 'from-before', get_option( '_transient_' . $translated ), 'left for cleanup-hp-transients --options' );
}
// ── count/purge legacy ──────────────────────────────────────────────────