hardening 2.3: 2meet-data-optimizer 1.3.0 (flat-table DDL outside web requests, routed count cache, audit fixes) #7
@@ -372,7 +372,23 @@ class TMDO_Post_Query_Router {
|
||||
if ( ! $counted ) {
|
||||
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" );
|
||||
return $request;
|
||||
}
|
||||
@@ -414,13 +430,18 @@ class TMDO_Post_Query_Router {
|
||||
if ( empty( $count_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' );
|
||||
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP );
|
||||
if ( false === $total ) {
|
||||
global $wpdb;
|
||||
$total = (int) $wpdb->get_var( $count_sql );
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,31 @@ class PostQueryRouterFoundRowsTest extends TestCase {
|
||||
$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 {
|
||||
$GLOBALS['_wp_current_filter'] = 'update_post_metadata';
|
||||
$before = wp_cache_get_last_changed( 'posts' );
|
||||
|
||||
Reference in New Issue
Block a user