hardening 2.3: 2meet-data-optimizer 1.3.0 (flat-table DDL outside web requests, routed count cache, audit fixes) #7

Merged
wpdev merged 20 commits from hardening/2.3 into main 2026-09-27 18:19:18 +08:00
2 changed files with 48 additions and 2 deletions
Showing only changes of commit b187c1c4d3 - Show all commits
@@ -372,7 +372,23 @@ class TMDO_Post_Query_Router {
if ( ! $counted ) { if ( ! $counted ) {
return $request; return $request;
} }
$matches = preg_replace( '/\s+LIMIT\s+\d+(\s*,\s*\d+)?\s*$/i', '', $request ); global $wpdb;
$matches = (string) preg_replace( '/\s+LIMIT\s+\d+(\s*,\s*\d+)?\s*$/i', '', $request );
$not_in = " AND {$wpdb->posts}.ID NOT IN (";
// post__not_in (HivePress sends a random set per page) would give every
// search its own cache key: count without it (cached) minus the excluded
// posts that match (primary-key lookups, so no forced flat-first order).
if ( 1 === substr_count( $matches, $not_in ) && preg_match( '/' . preg_quote( $not_in, '/' ) . '([\d,]+)\)/', $matches, $m ) ) {
$excluded = str_replace( $m[0], " AND {$wpdb->posts}.ID IN ({$m[1]})", (string) preg_replace( '/^SELECT STRAIGHT_JOIN /', 'SELECT ', $matches ) );
$query->set(
self::COUNT_VAR,
array(
'SELECT COUNT(*) FROM ( ' . str_replace( $m[0], '', $matches ) . ' ) AS tmdo_found',
"SELECT COUNT(*) FROM ( {$excluded} ) AS tmdo_found",
)
);
return $request;
}
$query->set( self::COUNT_VAR, "SELECT COUNT(*) FROM ( {$matches} ) AS tmdo_found" ); $query->set( self::COUNT_VAR, "SELECT COUNT(*) FROM ( {$matches} ) AS tmdo_found" );
return $request; return $request;
} }
@@ -414,13 +430,18 @@ class TMDO_Post_Query_Router {
if ( empty( $count_sql ) ) { if ( empty( $count_sql ) ) {
return (string) ( $sql ?? '' ); return (string) ( $sql ?? '' );
} }
global $wpdb;
list( $count_sql, $excluded_sql ) = array_pad( (array) $count_sql, 2, '' );
$key = md5( $count_sql ) . ':' . wp_cache_get_last_changed( 'posts' ); $key = md5( $count_sql ) . ':' . wp_cache_get_last_changed( 'posts' );
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP ); $total = wp_cache_get( $key, self::COUNT_CACHE_GROUP );
if ( false === $total ) { if ( false === $total ) {
global $wpdb;
$total = (int) $wpdb->get_var( $count_sql ); $total = (int) $wpdb->get_var( $count_sql );
wp_cache_set( $key, $total, self::COUNT_CACHE_GROUP, HOUR_IN_SECONDS ); wp_cache_set( $key, $total, self::COUNT_CACHE_GROUP, HOUR_IN_SECONDS );
} }
if ( '' !== $excluded_sql ) {
$total = max( 0, (int) $total - (int) $wpdb->get_var( $excluded_sql ) );
}
return 'SELECT ' . (int) $total; return 'SELECT ' . (int) $total;
} }
@@ -121,6 +121,31 @@ class PostQueryRouterFoundRowsTest extends TestCase {
$this->assertSame( '1', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'recounted' ); $this->assertSame( '1', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'recounted' );
} }
/**
* HivePress excludes a different random set of posts on each search page;
* the total is the cached count without the exclusion minus a live count
* of the excluded posts that match (by primary key).
*/
public function test_excluded_posts_do_not_split_the_cached_total(): void {
global $wpdb;
$count = function ( string $not_in ) use ( $wpdb ): string {
list( $router, $query, $request ) = $this->routed();
$request = str_replace( ' GROUP BY', " AND {$wpdb->posts}.ID NOT IN ({$not_in}) GROUP BY", $request );
$router->posts_request( $request, $query );
return (string) $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) );
};
$this->assertSame( '1', $count( '3' ), 'prices 20, 30 match; 3 excluded' );
$this->assertSame( '0', $count( '2,3' ) );
$wpdb->query( 'UPDATE `' . self::TABLE . '` SET fr_price = 5 WHERE post_id = 3' ); // Not seen until posts change.
$this->assertSame( '2', $count( '1' ), 'one cached count serves every exclusion list; 1 does not match' );
$this->assertSame( '1', $count( '2' ) );
wp_cache_set_posts_last_changed();
$this->assertSame( '0', $count( '2' ), 'recounted' );
}
public function test_flat_writes_and_deletes_move_posts_last_changed(): void { public function test_flat_writes_and_deletes_move_posts_last_changed(): void {
$GLOBALS['_wp_current_filter'] = 'update_post_metadata'; $GLOBALS['_wp_current_filter'] = 'update_post_metadata';
$before = wp_cache_get_last_changed( 'posts' ); $before = wp_cache_get_last_changed( 'posts' );