get_cold_meta_keys( $post_type ); if ( empty( $cold_keys ) ) { return; } $group = 'wpdo_cold_' . sanitize_key( $post_type ); // Check which IDs are already cached. $uncached = array(); foreach ( $post_ids as $pid ) { $cached = wp_cache_get( "cold_{$pid}", $group ); if ( false === $cached ) { $uncached[] = (int) $pid; } } if ( empty( $uncached ) ) { return; } // Bulk fetch from cold table. global $wpdb; $table = TMDO_Zone_Cold::table( $post_type ); $placeholders = implode( ',', array_fill( 0, count( $uncached ), '%d' ) ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Table from TMDO_Zone_Cold::table(); $placeholders built from array_fill with %d. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, data FROM `{$table}` WHERE post_id IN ({$placeholders})", ...$uncached ), ARRAY_A ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare // Get cache TTL from registry. $fields = TMDO_Schema_Registry::instance()->get_zone_fields_for_type( 'cold', $post_type ); $ttl = HOUR_IN_SECONDS; foreach ( $fields as $field ) { if ( ! empty( $field['cache_ttl'] ) ) { $ttl = (int) $field['cache_ttl']; break; } } // Index fetched rows by post_id. $fetched = array(); foreach ( $rows ?: array() as $row ) { $data = json_decode( $row['data'], true ); $fetched[ (int) $row['post_id'] ] = is_array( $data ) ? $data : array(); } // Cache all results (including empty arrays for posts with no cold data). foreach ( $uncached as $pid ) { $data = $fetched[ $pid ] ?? array(); wp_cache_set( "cold_{$pid}", $data, $group, $ttl ); } } /** * Warm the cache for a single post after it's saved/updated. * * Hooked to 'save_post' to ensure fresh data is in cache. * * @param int $post_id Post ID. * @param string $post_type Post type. */ public static function warm_post( int $post_id, string $post_type ): void { $cold_keys = TMDO_Schema_Registry::instance()->get_cold_meta_keys( $post_type ); if ( empty( $cold_keys ) ) { return; } // Force a fresh read from DB (bypasses cache). $group = 'wpdo_cold_' . sanitize_key( $post_type ); wp_cache_delete( "cold_{$post_id}", $group ); // Re-read will populate cache. TMDO_Zone_Cold::get_blob( $post_id, $post_type ); } /** * Flush all cold cache for a post type. * * Useful after bulk imports or schema changes. * * @param string $post_type Post type to flush. */ public static function flush_group( string $post_type ): void { $group = 'wpdo_cold_' . sanitize_key( $post_type ); if ( function_exists( 'wp_cache_flush_group' ) ) { wp_cache_flush_group( $group ); } // If wp_cache_flush_group is not available (older WP or no object cache), // individual entries will expire via TTL. } /** * Get cache statistics for the admin dashboard. * * @return array{groups: array, prefetch_support: bool} */ public static function get_stats(): array { $registry = TMDO_Schema_Registry::instance(); $cold_types = $registry->get_cold_post_types(); $groups = array(); foreach ( $cold_types as $pt ) { $group = 'wpdo_cold_' . sanitize_key( $pt ); $keys = $registry->get_cold_meta_keys( $pt ); $fields = $registry->get_zone_fields_for_type( 'cold', $pt ); $ttl = HOUR_IN_SECONDS; foreach ( $fields as $field ) { if ( ! empty( $field['cache_ttl'] ) ) { $ttl = (int) $field['cache_ttl']; break; } } $groups[] = array( 'post_type' => $pt, 'group' => $group, 'meta_keys' => $keys, 'ttl' => $ttl, ); } return array( 'groups' => $groups, 'prefetch_support' => true, 'flush_support' => function_exists( 'wp_cache_flush_group' ), ); } /** * Register WordPress hooks for cache warming. */ public static function register_hooks(): void { add_action( 'save_post', array( __CLASS__, 'on_save_post' ), 99, 2 ); // Prefetch on HivePress archive pages. add_action( 'loop_start', array( __CLASS__, 'on_loop_start' ), 10, 1 ); } /** * Warm cache after post save. * * @param int $post_id Post ID. * @param \WP_Post $post Post object. * @return void */ public static function on_save_post( int $post_id, \WP_Post $post ): void { if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; } $cold_keys = TMDO_Schema_Registry::instance()->get_cold_meta_keys( $post->post_type ); if ( ! empty( $cold_keys ) ) { self::warm_post( $post_id, $post->post_type ); } } /** * Prefetch cold data at the start of a main query loop. * * @param \WP_Query $query The current WP_Query object. * @return void */ public static function on_loop_start( \WP_Query $query ): void { if ( ! $query->is_main_query() || is_admin() ) { return; } $posts = $query->posts; if ( empty( $posts ) ) { return; } $post_type = $query->get( 'post_type' ); if ( is_array( $post_type ) ) { $post_type = reset( $post_type ); } if ( ! $post_type ) { return; } $cold_keys = TMDO_Schema_Registry::instance()->get_cold_meta_keys( $post_type ); if ( empty( $cold_keys ) ) { return; } $post_ids = wp_list_pluck( $posts, 'ID' ); self::prefetch( $post_ids, $post_type ); } }