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 ) {
|
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' );
|
||||||
|
|||||||
Reference in New Issue
Block a user