post_type = $post_type; } /** * Returns the module identifier. * * @return string Module name. */ public function get_module(): string { return 'hot_' . sanitize_key( $this->post_type ); } /** * Returns the zone identifier. * * @return string Zone name. */ public function get_zone(): string { return 'hot'; } /** * Returns the total number of posts to migrate. * * @return int Total post count. */ protected function count_source(): int { global $wpdb; $meta_keys = $this->get_meta_keys(); if ( empty( $meta_keys ) ) { return 0; } $placeholders = implode( ',', array_fill( 0, count( $meta_keys ), '%s' ) ); $args = array_merge( array( $this->post_type ), $meta_keys ); // Count distinct posts that have at least one hot field. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $placeholders built from array_fill with %s; $wpdb->postmeta/$wpdb->posts are core properties. return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT pm.post_id) FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key IN ({$placeholders})", ...$args ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber } /** * Migrates one batch of posts to the hot table. * * @param int $offset Starting post offset. * @return int Number of posts processed. */ protected function migrate_batch( int $offset ): int { global $wpdb; $meta_keys = $this->get_meta_keys(); if ( empty( $meta_keys ) ) { return 0; } $columns = TMDO_Schema_Registry::instance()->get_hot_columns( $this->post_type ); $key_map = $this->build_key_to_column_map(); $placeholders = implode( ',', array_fill( 0, count( $meta_keys ), '%s' ) ); // Get batch of distinct post IDs. $args = array_merge( array( $this->post_type ), $meta_keys, array( self::BATCH_SIZE, $offset ) ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $placeholders/$id_placeholders built from array_fill; $wpdb->postmeta/$wpdb->posts are core properties. $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT pm.post_id FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key IN ({$placeholders}) ORDER BY pm.post_id ASC LIMIT %d OFFSET %d", ...$args ) ); if ( empty( $post_ids ) ) { return 0; } // Ensure hot table exists. TMDO_Zone_Hot::ensure_table( $this->post_type ); // For each post, gather all hot meta and upsert. $id_placeholders = implode( ',', array_fill( 0, count( $post_ids ), '%d' ) ); $meta_rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id IN ({$id_placeholders}) AND meta_key IN ({$placeholders})", ...array_merge( array_map( 'intval', $post_ids ), $meta_keys ) ), ARRAY_A ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber // Group by post_id. $grouped = array(); foreach ( $meta_rows ?: array() as $row ) { $col = $key_map[ $row['meta_key'] ] ?? null; if ( $col ) { $grouped[ $row['post_id'] ][ $col ] = $row['meta_value']; } } // Upsert each post's hot data. foreach ( $grouped as $pid => $data ) { TMDO_Zone_Hot::set_many( (int) $pid, $this->post_type, $data ); } return count( $post_ids ); } /** * Verifies that the hot table row count is at least as large as the source. * * @return bool True if verification passes. */ public function verify_counts(): bool { global $wpdb; $source = $this->count_source(); $table = TMDO_Zone_Hot::table( $this->post_type ); $target = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from TMDO_Zone_Hot::table() return $target >= $source; } // ── Private helpers ─────────────────────────────────────────────────── /** * Get all registered hot meta_keys for this post type. */ private function get_meta_keys(): array { $fields = TMDO_Schema_Registry::instance()->get_zone_fields_for_type( 'hot', $this->post_type ); return array_column( $fields, 'meta_key' ); } /** * Build meta_key → column_name map. */ private function build_key_to_column_map(): array { $fields = TMDO_Schema_Registry::instance()->get_zone_fields_for_type( 'hot', $this->post_type ); $map = array(); foreach ( $fields as $field ) { $map[ $field['meta_key'] ] = $field['column']; } return $map; } }