hardening 2.3: 2meet-data-optimizer 1.3.0 (flat-table DDL outside web requests, routed count cache, audit fixes) #7
@@ -433,8 +433,11 @@ class TMDO_Post_Query_Router {
|
|||||||
global $wpdb;
|
global $wpdb;
|
||||||
list( $count_sql, $excluded_sql ) = array_pad( (array) $count_sql, 2, '' );
|
list( $count_sql, $excluded_sql ) = array_pad( (array) $count_sql, 2, '' );
|
||||||
|
|
||||||
$key = md5( $count_sql ) . ':' . wp_cache_get_last_changed( 'posts' );
|
// Keyword searches carry wpdb's placeholder escape for "%", which is new
|
||||||
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP );
|
// in every request: without it the key would never match again.
|
||||||
|
$stable = method_exists( $wpdb, 'remove_placeholder_escape' ) ? $wpdb->remove_placeholder_escape( $count_sql ) : $count_sql;
|
||||||
|
$key = md5( $stable ) . ':' . wp_cache_get_last_changed( 'posts' );
|
||||||
|
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP );
|
||||||
if ( false === $total ) {
|
if ( false === $total ) {
|
||||||
$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 );
|
||||||
|
|||||||
@@ -157,6 +157,49 @@ class PostQueryRouterFoundRowsTest extends TestCase {
|
|||||||
$this->assertSame( '0', $count( '2' ), 'recounted' );
|
$this->assertSame( '0', $count( '2' ), 'recounted' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyword searches carry wpdb's placeholder escape for "%", which changes
|
||||||
|
* with every request: the cache key uses the SQL without it, or no search
|
||||||
|
* with a keyword ever hits the cached count.
|
||||||
|
*/
|
||||||
|
public function test_the_cached_count_survives_a_new_placeholder_escape(): void {
|
||||||
|
global $wpdb;
|
||||||
|
$real = $wpdb;
|
||||||
|
$wpdb = new class( $real ) {
|
||||||
|
public int $counts = 0;
|
||||||
|
|
||||||
|
public function __construct( private object $db ) {}
|
||||||
|
|
||||||
|
public function remove_placeholder_escape( string $sql ): string {
|
||||||
|
return (string) preg_replace( '/\{[0-9a-f]{8}\}/', '%', $sql );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call( string $name, array $args ) {
|
||||||
|
if ( 'get_var' === $name && str_contains( (string) $args[0], 'COUNT(*)' ) ) {
|
||||||
|
++$this->counts;
|
||||||
|
}
|
||||||
|
return $this->db->$name( ...$args );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get( string $name ) {
|
||||||
|
return $this->db->$name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
foreach ( array( '{1a2b3c4d}', '{9f8e7d6c}' ) as $escape ) { // Two requests.
|
||||||
|
list( $router, $query, $request ) = $this->routed();
|
||||||
|
$request = str_replace( ' GROUP BY', " AND '{$escape}' = '{$escape}' GROUP BY", $request );
|
||||||
|
$router->posts_request( $request, $query );
|
||||||
|
$this->assertSame( '2', $real->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ) );
|
||||||
|
}
|
||||||
|
$counts = $wpdb->counts;
|
||||||
|
} finally {
|
||||||
|
$wpdb = $real;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertSame( 1, $counts, 'counted once' );
|
||||||
|
}
|
||||||
|
|
||||||
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