post_type = $post_type; } /** * Returns the module identifier. * * @return string Module name. */ public function get_module(): string { return TMDO_Zone_Router::module_name( 'cold', $this->post_type ); } /** * Returns the zone identifier. * * @return string Zone name. */ public function get_zone(): string { return 'cold'; } /** * Returns the total number of posts to migrate. * * @return int Total post count. */ protected function count_source(): int { global $wpdb; $meta_keys = TMDO_Schema_Registry::instance()->get_cold_meta_keys( $this->post_type ); 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 cold field. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $placeholders from array_fill; $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 cold table. * * @param int $offset Starting post offset. * @return int Number of posts processed. */ protected function migrate_batch( int $offset ): int { global $wpdb; $meta_keys = TMDO_Schema_Registry::instance()->get_cold_meta_keys( $this->post_type ); if ( empty( $meta_keys ) ) { return 0; } $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 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 ) ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber // Ensure cold table exists before any write (even if this batch is empty). TMDO_Zone_Cold::ensure_table( $this->post_type ); if ( empty( $post_ids ) ) { return 0; } // Fetch all cold meta for these posts. $id_placeholders = implode( ',', array_fill( 0, count( $post_ids ), '%d' ) ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber $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 ) { $grouped[ $row['post_id'] ][ $row['meta_key'] ] = $row['meta_value']; } // Write each post's cold data as a JSON blob. foreach ( $grouped as $pid => $data ) { TMDO_Zone_Cold::set_many( (int) $pid, $this->post_type, $data ); } return count( $post_ids ); } /** * Verifies that the cold 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_Cold::table( $this->post_type ); $target = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from TMDO_Zone_Cold::table() return $target >= $source; } }