get_var( $wpdb->prepare( "SELECT meta_value FROM `{$table}` WHERE post_id = %d AND meta_key = %s AND (expires_at IS NULL OR expires_at > %s) LIMIT 1", $post_id, $meta_key, $now ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared return $val; } /** * Read all warm values for a post. * * @param int $post_id Post ID. * @return array meta_key => meta_value pairs. */ public static function get_all( int $post_id ): array { global $wpdb; $table = self::table(); $now = TMDO_DB::now(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from TMDO_Zone_Warm::table() via TMDO_DB::table(). $rows = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM `{$table}` WHERE post_id = %d AND (expires_at IS NULL OR expires_at > %s)", $post_id, $now ), ARRAY_A ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $result = array(); foreach ( $rows ?: array() as $row ) { $result[ $row['meta_key'] ] = $row['meta_value']; } return $result; } /** * Set a value in the warm zone with optional TTL. * * @param int $post_id Post ID. * @param string $meta_key Meta key. * @param string $value Value to store. * @param int|null $ttl TTL in seconds. Null = no expiry. */ public static function set( int $post_id, string $meta_key, string $value, ?int $ttl = null ): void { $table = self::table(); $now = TMDO_DB::now(); $expires_at = ( $ttl && $ttl > 0 ) ? gmdate( 'Y-m-d H:i:s', time() + $ttl ) : null; TMDO_DB::upsert( $table, array( 'post_id' => $post_id, 'meta_key' => $meta_key, 'meta_value' => $value, 'expires_at' => $expires_at, 'created_at' => $now, ), array( 'meta_value', 'expires_at' ), array( 'post_id', 'meta_key' ), array( '%d', '%s', '%s', '%s', '%s' ) ); } /** * Atomically increment an integer counter in the warm zone. * * Uses INSERT ... ON DUPLICATE KEY UPDATE to avoid the read-then-write race * condition present in get()+set() patterns. * * @param int $post_id Post ID. * @param string $meta_key Counter key. * @param int $by Amount to increment (default 1). * @param int|null $ttl TTL in seconds. Null = no expiry. */ public static function increment( int $post_id, string $meta_key, int $by = 1, ?int $ttl = null ): void { global $wpdb; $table = self::table(); $now = TMDO_DB::now(); $expires_at = ( $ttl && $ttl > 0 ) ? gmdate( 'Y-m-d H:i:s', time() + $ttl ) : null; // phpcs:disable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table from self::table() $wpdb->query( $wpdb->prepare( "INSERT INTO `{$table}` (post_id, meta_key, meta_value, expires_at, created_at) VALUES (%d, %s, %d, %s, %s) ON DUPLICATE KEY UPDATE meta_value = CAST(COALESCE(meta_value, 0) AS SIGNED) + %d", $post_id, $meta_key, $by, $expires_at, $now, $by ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Delete a specific key from the warm zone. * * @param int $post_id Post ID. * @param string $meta_key Meta key to delete. * @return void */ public static function delete( int $post_id, string $meta_key ): void { global $wpdb; $wpdb->delete( self::table(), array( 'post_id' => $post_id, 'meta_key' => $meta_key, ), array( '%d', '%s' ) ); } /** * Delete all warm entries for a post. * * @param int $post_id Post ID. * @return void */ public static function delete_all( int $post_id ): void { global $wpdb; $wpdb->delete( self::table(), array( 'post_id' => $post_id ), array( '%d' ) ); } /** * Purge all expired entries (called by cron). * * @return int Number of rows deleted. */ public static function purge_expired(): int { global $wpdb; $table = self::table(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name from TMDO_Zone_Warm::table() via TMDO_DB::table(). return (int) $wpdb->query( $wpdb->prepare( "DELETE FROM `{$table}` WHERE expires_at IS NOT NULL AND expires_at < %s", TMDO_DB::now() ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } }