] * : Which garbage class to address. Default: all. * --- * default: all * options: * - all * - transients * - wp_old_date * - edit_locks * --- * * [--dry-run] * : Show row counts without deleting. * * [--confirm] * : Required to actually DELETE rows. Mutually exclusive with --dry-run. * * ## EXAMPLES * * wp wpdo postmeta-cleanup --dry-run * wp wpdo postmeta-cleanup --target=transients --dry-run * wp wpdo postmeta-cleanup --confirm * wp wpdo postmeta-cleanup --target=edit_locks --confirm * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function postmeta_cleanup( $args, $assoc_args ): void { $target = (string) ( $assoc_args['target'] ?? TMDO_Postmeta_Cleaner::TARGET_ALL ); $dry_run = isset( $assoc_args['dry-run'] ); $confirm = isset( $assoc_args['confirm'] ); if ( ! in_array( $target, TMDO_Postmeta_Cleaner::VALID_TARGETS, true ) ) { WP_CLI::error( 'Invalid --target. Choose: ' . implode( ', ', TMDO_Postmeta_Cleaner::VALID_TARGETS ) ); } if ( $dry_run && $confirm ) { WP_CLI::error( '--dry-run and --confirm are mutually exclusive.' ); } // Default safety: refuse to run without an explicit choice. if ( ! $dry_run && ! $confirm ) { WP_CLI::error( "Refusing to run without an explicit choice. Pass --dry-run to preview, or --confirm to delete.\n" . 'Example: wp wpdo postmeta-cleanup --dry-run' ); } if ( $dry_run ) { $counts = TMDO_Postmeta_Cleaner::count_garbage( $target ); self::render_table( $counts, 'would delete' ); WP_CLI::success( sprintf( '[dry-run] %d row(s) would be deleted. Re-run with --confirm to apply.', $counts['total'] ) ); return; } // $confirm path. $deleted = TMDO_Postmeta_Cleaner::delete_garbage( $target ); self::render_table( $deleted, 'deleted' ); if ( class_exists( 'TMDO_Logger' ) ) { TMDO_Logger::info( 'postmeta_cleanup', array( 'target' => $target, 'transients' => $deleted['transients'], 'wp_old_date' => $deleted['wp_old_date'], 'edit_locks' => $deleted['edit_locks'], 'total' => $deleted['total'], ) ); } WP_CLI::success( sprintf( 'Deleted %d row(s) from wp_postmeta.', $deleted['total'] ) ); } /** * Render a table of bucket → count. * * @param array $counts Output from TMDO_Postmeta_Cleaner::count_garbage() / delete_garbage(). * @param string $verb Column header verb ('would delete' / 'deleted'). */ private static function render_table( array $counts, string $verb ): void { $rows = array( array( 'bucket' => 'transients', $verb => $counts['transients'], 'rule' => '_transient_% OR _transient_timeout_%', ), array( 'bucket' => 'wp_old_date', $verb => $counts['wp_old_date'], 'rule' => "meta_key = '_wp_old_date'", ), array( 'bucket' => 'edit_locks', $verb => $counts['edit_locks'], 'rule' => '_edit_lock older than 24h', ), array( 'bucket' => 'TOTAL', $verb => $counts['total'], 'rule' => '', ), ); WP_CLI\Utils\format_items( 'table', $rows, array( 'bucket', $verb, 'rule' ) ); } // ── post-diagnose (v2.9.3) ──────────────────────────────────────────────── /** * Show wp_posts:wp_postmeta ratio + per-group EAV residue / flat row count. * * Read-only command. Mirrors `wp wpdo member-audit` for the post entity. * * ## EXAMPLES * * wp wpdo post-diagnose * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments (unused). */ public function post_diagnose( $args, $assoc_args ): void { $result = TMDO_Post_Migration::diagnose(); WP_CLI::log( sprintf( 'Posts: %s', number_format_i18n( $result['posts'] ) ) ); WP_CLI::log( sprintf( 'Postmeta: %s', number_format_i18n( $result['postmeta'] ) ) ); WP_CLI::log( sprintf( 'Ratio: 1:%s', $result['ratio'] ) ); WP_CLI::log( sprintf( 'Mode: %s', $result['mode'] ) ); WP_CLI::log( '' ); $rows = array(); foreach ( $result['groups'] as $name => $g ) { $rows[] = array( 'group' => $name, 'post_type' => $g['post_type'] ?: '(any)', 'keys' => count( $g['keys'] ), 'eav_rows' => $g['eav_rows'], 'flat_rows' => $g['flat_rows'], ); } WP_CLI\Utils\format_items( 'table', $rows, array( 'group', 'post_type', 'keys', 'eav_rows', 'flat_rows' ) ); } // ── post-migrate-group (v2.9.3) ─────────────────────────────────────────── /** * Backfill one post entity group from wp_postmeta into its flat table * via a single bulk SQL pivot (ON DUPLICATE KEY UPDATE). * * Idempotent — safe to re-run. Skips JSON-typed fields (handled by the * row-by-row backfill phase in v2.9.4+). * * ## OPTIONS * * --group= * : Entity group name. Required. * --- * options: * - wp_core * - attachment * - wc_product * - hp_listing_core * - hp_request_core * - hp_vendor_core * - nav_menu_item * --- * * ## EXAMPLES * * wp wpdo post-migrate-group --group=wc_product * wp wpdo post-migrate-group --group=hp_listing_core * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function post_migrate_group( $args, $assoc_args ): void { $group = (string) ( $assoc_args['group'] ?? '' ); if ( '' === $group ) { WP_CLI::error( 'Missing required --group=.' ); } try { $result = TMDO_Post_Migration::backfill_group( $group ); } catch ( \Throwable $e ) { WP_CLI::error( $e->getMessage() ); } if ( class_exists( 'TMDO_Logger' ) ) { TMDO_Logger::info( 'post_migrate_group', array( 'group' => $result['group'], 'post_type' => $result['post_type'], 'migrated' => $result['migrated'], ) ); } WP_CLI::success( sprintf( 'Backfilled %s (post_type=%s): %d post(s) migrated.', $result['group'], $result['post_type'] ?: '(any)', $result['migrated'] ) ); } // ── post-cleanup (v2.9.3) ───────────────────────────────────────────────── /** * DELETE managed-key wp_postmeta rows after cutover. Requires post mode * to be aeav_only (the flat table is the authoritative source). * * SAFETY: requires --confirm. * * ## OPTIONS * * [--dry-run] * : Preview row count only. * * [--confirm] * : Required to actually DELETE rows. * * ## EXAMPLES * * wp wpdo post-cleanup --dry-run * wp wpdo post-cleanup --confirm * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function post_cleanup( $args, $assoc_args ): void { $dry_run = isset( $assoc_args['dry-run'] ); $confirm = isset( $assoc_args['confirm'] ); if ( $dry_run && $confirm ) { WP_CLI::error( '--dry-run and --confirm are mutually exclusive.' ); } if ( ! $dry_run && ! $confirm ) { WP_CLI::error( "Refusing to run without an explicit choice. Pass --dry-run to preview, or --confirm to delete.\n" . 'Example: wp wpdo post-cleanup --dry-run' ); } $diagnose = TMDO_Post_Migration::diagnose(); $keys = TMDO_Post_Migration::get_managed_keys(); global $wpdb; $placeholders = implode( ',', array_fill( 0, count( $keys ), '%s' ) ); $sql = "SELECT COUNT(*) FROM {$wpdb->postmeta} WHERE meta_key IN ({$placeholders})"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare $candidate = (int) $wpdb->get_var( $wpdb->prepare( $sql, ...$keys ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared if ( $dry_run ) { WP_CLI::log( sprintf( 'Mode: %s', $diagnose['mode'] ) ); WP_CLI::log( sprintf( 'Managed keys: %d', count( $keys ) ) ); WP_CLI::success( sprintf( '[dry-run] %d wp_postmeta row(s) would be deleted. Re-run with --confirm.', $candidate ) ); return; } // $confirm path — TMDO_Post_Migration::cleanup() enforces mode=aeav_only. try { $result = TMDO_Post_Migration::cleanup(); } catch ( \Throwable $e ) { WP_CLI::error( $e->getMessage() ); } if ( class_exists( 'TMDO_Logger' ) ) { TMDO_Logger::info( 'post_cleanup', array( 'deleted' => $result['deleted'], ) ); } WP_CLI::success( sprintf( 'Deleted %d wp_postmeta row(s).', $result['deleted'] ) ); } // ── cleanup-hp-transients (v2.11.5) ─────────────────────────────────────── /** * Purge legacy `_transient_hp_*` rows from wp_postmeta. * * HivePress (`hivepress/includes/components/class-cache.php`) writes * per-post TTL caches as `_transient_` / `_transient_timeout_` * postmeta rows. v2.11.5 ships `TMDO_Hivepress_Transient_Filter` to * intercept new writes and reroute them to wp_options. This command does * the one-time historical cleanup — DELETE-ing all such existing rows * from wp_postmeta. After cleanup, HivePress re-fetches on demand. * * Safe to run with the filter enabled; the filter prevents new postmeta * writes from re-bloating the table. * * ## OPTIONS * * [--dry-run] * : Preview row count only. * * [--confirm] * : Required to actually DELETE rows. * * ## EXAMPLES * * wp wpdo cleanup-hp-transients --dry-run * wp wpdo cleanup-hp-transients --confirm * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function cleanup_hp_transients( $args, $assoc_args ): void { unset( $args ); $dry_run = isset( $assoc_args['dry-run'] ); $confirm = isset( $assoc_args['confirm'] ); if ( $dry_run && $confirm ) { WP_CLI::error( '--dry-run and --confirm are mutually exclusive.' ); } if ( ! $dry_run && ! $confirm ) { WP_CLI::error( "Refusing to run without explicit choice. Pass --dry-run or --confirm.\n" . 'Example: wp wpdo cleanup-hp-transients --dry-run' ); } if ( ! class_exists( 'TMDO_Hivepress_Transient_Filter' ) ) { WP_CLI::error( 'TMDO_Hivepress_Transient_Filter not loaded.' ); } $count = TMDO_Hivepress_Transient_Filter::count_legacy_postmeta_rows(); if ( $dry_run ) { WP_CLI::success( sprintf( '[dry-run] %d HivePress transient row(s) in wp_postmeta would be deleted. Re-run with --confirm.', $count ) ); return; } $deleted = TMDO_Hivepress_Transient_Filter::purge_legacy_postmeta_rows(); if ( class_exists( 'TMDO_Logger' ) ) { TMDO_Logger::info( 'cleanup_hp_transients', array( 'deleted' => $deleted ) ); } WP_CLI::success( sprintf( 'Deleted %d HivePress transient row(s) from wp_postmeta. Future writes auto-route to wp_options.', $deleted ) ); } // ── post-cutover-legacy (v2.9.5) ────────────────────────────────────────── /** * Non-destructive copy of a legacy `wpdo_hot_` zone table * into the new `wp_wpdo_post_` flat table. * * The legacy table is left UNTOUCHED (safety net for v3.0.0 rollback). * Idempotent — re-running is safe. * * Currently supports hp_listing only (the only post_type with a * legacy hot table on production deployments). Other post_types skip * the cutover and rely on direct wp_postmeta backfill. * * ## OPTIONS * * --post-type= * : Post type to cutover. * --- * default: hp_listing * options: * - hp_listing * --- * * [--dry-run] * : Preview row counts only. * * [--confirm] * : Required to actually run the copy. * * ## EXAMPLES * * wp wpdo post-cutover-legacy --dry-run * wp wpdo post-cutover-legacy --confirm * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function post_cutover_legacy( $args, $assoc_args ): void { $post_type = (string) ( $assoc_args['post-type'] ?? 'hp_listing' ); $dry_run = isset( $assoc_args['dry-run'] ); $confirm = isset( $assoc_args['confirm'] ); if ( $dry_run && $confirm ) { WP_CLI::error( '--dry-run and --confirm are mutually exclusive.' ); } if ( ! $dry_run && ! $confirm ) { WP_CLI::error( "Refusing to run without an explicit choice. Pass --dry-run to preview, or --confirm to run.\n" . 'Example: wp wpdo post-cutover-legacy --dry-run' ); } // Map post_type → (hot_table, flat_table, group). $mapping = array( 'hp_listing' => array( 'hot' => 'wpdo_hot_hp_listing', 'flat' => 'wpdo_post_hp_listing_core', 'group' => 'hp_listing_core', ), ); if ( ! isset( $mapping[ $post_type ] ) ) { WP_CLI::error( 'Unsupported --post-type: ' . $post_type ); } global $wpdb; $hot_table = $wpdb->prefix . $mapping[ $post_type ]['hot']; $flat_table = $wpdb->prefix . $mapping[ $post_type ]['flat']; // Pre-flight: verify hot table exists and report current state. $hot_exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $hot_table ) ); if ( ! $hot_exists ) { WP_CLI::warning( "Legacy hot table not present: {$hot_table}. Nothing to do." ); return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $hot_rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$hot_table}`" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $flat_rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$flat_table}`" ); WP_CLI::log( sprintf( 'Hot (%s): %s rows', $hot_table, number_format_i18n( $hot_rows ) ) ); WP_CLI::log( sprintf( 'Flat (%s): %s rows', $flat_table, number_format_i18n( $flat_rows ) ) ); WP_CLI::log( '' ); if ( $dry_run ) { WP_CLI::success( sprintf( '[dry-run] Would copy %s rows from hot → flat. Re-run with --confirm to apply.', number_format_i18n( $hot_rows ) ) ); return; } // $confirm path. try { $result = TMDO_Post_Migration::copy_legacy_hot_table( $post_type, $hot_table, $flat_table ); $verify = TMDO_Post_Migration::verify_legacy_cutover( $hot_table, $flat_table ); } catch ( \Throwable $e ) { WP_CLI::error( $e->getMessage() ); } if ( class_exists( 'TMDO_Logger' ) ) { TMDO_Logger::info( 'post_cutover_legacy', array( 'post_type' => $post_type, 'hot_table' => $hot_table, 'flat_table' => $flat_table, 'copied' => $result['copied'], 'common_columns' => $result['common_columns'], 'verify' => $verify, ) ); } WP_CLI::log( sprintf( 'Copied %d row(s). Common columns: %s', $result['copied'], implode( ', ', $result['common_columns'] ) ) ); WP_CLI::log( sprintf( 'Verify: hot=%d flat=%d mismatched=%d ok=%s', $verify['hot_rows'], $verify['flat_rows'], $verify['mismatched_rows'], $verify['ok'] ? 'yes' : 'NO' ) ); if ( ! $verify['ok'] ) { WP_CLI::error( 'Verification failed — flat table missing rows. Legacy hot table left intact for retry.' ); } WP_CLI::success( sprintf( 'Legacy cutover complete (%s). Hot table preserved as safety net for v3.0.0 rollback.', $post_type ) ); } // ── post-benchmark (v2.10.2) ────────────────────────────────────────────── /** * Compare query latency between wp_postmeta JOIN and flat-table JOIN * for a single meta_key/value combination. * * Speed-up = postmeta_avg_ms / flat_avg_ms. Higher is better. * * Read-only — does not modify any table or change post mode. Safe to run * in any post mode (the benchmark always queries both paths regardless). * * ## OPTIONS * * --post-type= * : Post type to filter by (e.g. product, hp_listing). * * --meta-key= * : Meta key to query (must be registered in entity registry for $post_type). * * [--compare=] * : Comparison operator. Default: =. * --- * default: = * options: * - "=" * - "!=" * - "<" * - "<=" * - ">" * - ">=" * - "LIKE" * --- * * --value= * : Value to compare against. * * [--samples=] * : Number of times to run each query. Default: 50. * * ## EXAMPLES * * wp wpdo post-benchmark --post-type=hp_listing --meta-key=hp_price --compare=">=" --value=100 * wp wpdo post-benchmark --post-type=product --meta-key=_price --compare="=" --value=99 --samples=200 * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function post_benchmark( $args, $assoc_args ): void { $post_type = (string) ( $assoc_args['post-type'] ?? '' ); $meta_key = (string) ( $assoc_args['meta-key'] ?? '' ); $compare = (string) ( $assoc_args['compare'] ?? '=' ); $value = (string) ( $assoc_args['value'] ?? '' ); $samples = max( 1, (int) ( $assoc_args['samples'] ?? 50 ) ); if ( '' === $post_type || '' === $meta_key ) { WP_CLI::error( 'Both --post-type and --meta-key are required.' ); } // Look up the entity group for this meta_key to find the flat table. $field = TMDO_Entity_Registry::get_field( 'post', $meta_key ); if ( ! $field ) { WP_CLI::error( "Meta key '{$meta_key}' is not registered for entity_type='post'." ); } global $wpdb; $flat_table = $wpdb->prefix . 'wpdo_post_' . sanitize_key( $field['group'] ); try { $result = TMDO_Post_Migration::benchmark_query( $post_type, $meta_key, $compare, $value, $flat_table, $samples ); } catch ( \Throwable $e ) { WP_CLI::error( $e->getMessage() ); } WP_CLI::log( sprintf( 'Benchmark: %s.%s %s %s (samples=%d)', $post_type, $meta_key, $compare, $value, $samples ) ); WP_CLI::log( '' ); WP_CLI::log( sprintf( ' postmeta JOIN: %.3f ms avg (matched %d rows)', $result['postmeta_avg_ms'], $result['postmeta_rows'] ) ); WP_CLI::log( sprintf( ' flat JOIN: %.3f ms avg (matched %d rows)', $result['flat_avg_ms'], $result['flat_rows'] ) ); WP_CLI::log( '' ); WP_CLI::log( sprintf( ' → Speedup: %.2fx', $result['speedup'] ) ); // Persist to wpdo_benchmarks table. $bench_table = $wpdb->prefix . 'wpdo_benchmarks'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->insert( $bench_table, array( 'module' => 'post_router_' . $post_type, 'zone' => 'flat', 'query_type' => 'meta_query_' . $meta_key, 'native_ms' => $result['postmeta_avg_ms'], 'custom_ms' => $result['flat_avg_ms'], 'sample_size' => $samples, 'created_at' => gmdate( 'Y-m-d H:i:s' ), ), array( '%s', '%s', '%s', '%f', '%f', '%d', '%s' ) ); WP_CLI::success( sprintf( 'Benchmark recorded to %s.', $bench_table ) ); } // ── post-shadow-report (v2.10.3) ────────────────────────────────────────── /** * Show shadow_read divergence stats for entity_type='post'. * * When post mode is shadow_read, the TMDO_Post_Shadow_Verifier cron job * runs hourly and writes any flat vs wp_postmeta divergences to * wpdo_shadow_diffs. This command surfaces a 24h aggregate. * * Optionally runs an immediate sample-compare pass via --run-now. * * ## OPTIONS * * [--hours=] * : Aggregation window in hours. Default: 24. * * [--limit=] * : Max recent diff rows to display. Default: 10. * * [--run-now] * : Execute one sample-compare tick immediately (alongside the report). * * ## EXAMPLES * * wp wpdo post-shadow-report * wp wpdo post-shadow-report --hours=48 --limit=20 * wp wpdo post-shadow-report --run-now * * @param array $args Positional arguments (unused). * @param array $assoc_args Named arguments. */ public function post_shadow_report( $args, $assoc_args ): void { $hours = max( 1, (int) ( $assoc_args['hours'] ?? 24 ) ); $limit = max( 1, (int) ( $assoc_args['limit'] ?? 10 ) ); $run_now = isset( $assoc_args['run-now'] ); $mode = TMDO_Mode_Manager::get( 'post' ); WP_CLI::log( sprintf( 'Post mode: %s', $mode ) ); WP_CLI::log( sprintf( 'Window: last %d hours', $hours ) ); WP_CLI::log( '' ); if ( $run_now ) { WP_CLI::log( 'Running sample-compare for all groups...' ); $totals = array( 'sampled' => 0, 'matched' => 0, 'diffs' => 0, 'missing_flat' => 0, 'missing_postmeta' => 0, ); global $wpdb; foreach ( TMDO_Entity_Registry::get_groups_for_type( 'post' ) as $group ) { $keys = TMDO_Entity_Registry::get_group_keys( 'post', $group ); if ( empty( $keys ) ) { continue; } // Use the verifier's own group→post_type mapping reflectively. $pt = self::group_post_type_for_cli( $group ); if ( null === $pt ) { continue; } $flat = $wpdb->prefix . 'wpdo_post_' . sanitize_key( $group ); try { $res = TMDO_Post_Shadow_Verifier::sample_compare( $pt, $group, $flat, $keys, 50 ); foreach ( $totals as $k => $_ ) { $totals[ $k ] += (int) ( $res[ $k ] ?? 0 ); } WP_CLI::log( sprintf( ' %-20s sampled=%d matched=%d diffs=%d miss_flat=%d miss_pm=%d', $group, $res['sampled'], $res['matched'], $res['diffs'], $res['missing_flat'], $res['missing_postmeta'] ) ); } catch ( \Throwable $e ) { WP_CLI::warning( " {$group}: " . $e->getMessage() ); } } WP_CLI::log( '' ); WP_CLI::log( sprintf( 'Run-now totals: sampled=%d matched=%d diffs=%d miss_flat=%d miss_pm=%d', $totals['sampled'], $totals['matched'], $totals['diffs'], $totals['missing_flat'], $totals['missing_postmeta'] ) ); WP_CLI::log( '' ); } // 24h aggregate from wpdo_shadow_diffs. $stats = TMDO_Post_Shadow_Verifier::diff_stats( $hours ); WP_CLI::log( sprintf( 'Total diffs (last %dh): %d', $hours, $stats['total'] ) ); if ( ! empty( $stats['by_key'] ) ) { WP_CLI::log( '' ); $rows = array(); foreach ( $stats['by_key'] as $key => $count ) { $rows[] = array( 'meta_key' => $key, 'diffs' => $count, ); } WP_CLI\Utils\format_items( 'table', $rows, array( 'meta_key', 'diffs' ) ); } // Recent diff rows. if ( $stats['total'] > 0 ) { $recent = TMDO_Post_Shadow_Verifier::recent_diffs( $limit ); if ( ! empty( $recent ) ) { WP_CLI::log( '' ); WP_CLI::log( sprintf( 'Recent %d diff(s):', $limit ) ); WP_CLI\Utils\format_items( 'table', $recent, array( 'entity_id', 'meta_key', 'postmeta_value', 'zone_value', 'ts' ) ); } } WP_CLI::success( 'Shadow report complete.' ); } /** * Helper: map entity group to its primary post_type for the run-now CLI path. * Mirror of TMDO_Post_Shadow_Verifier's private mapping. * * @param string $group Entity group name. * @return string|null */ private static function group_post_type_for_cli( string $group ): ?string { switch ( $group ) { case 'attachment': return 'attachment'; case 'wc_product': return 'product'; case 'hp_listing_core': return 'hp_listing'; case 'hp_request_core': return 'hp_request'; case 'hp_vendor_core': return 'hp_vendor'; case 'nav_menu_item': return 'nav_menu_item'; case 'wp_core': default: return null; } } } // ── Register subcommands ────────────────────────────────────────────────────── WP_CLI::add_command( 'wpdo postmeta-cleanup', array( 'TMDO_CLI_Post', 'postmeta_cleanup' ) ); WP_CLI::add_command( 'tmdo postmeta-cleanup', array( 'TMDO_CLI_Post', 'postmeta_cleanup' ) ); WP_CLI::add_command( 'wpdo post-diagnose', array( 'TMDO_CLI_Post', 'post_diagnose' ) ); WP_CLI::add_command( 'tmdo post-diagnose', array( 'TMDO_CLI_Post', 'post_diagnose' ) ); WP_CLI::add_command( 'wpdo post-migrate-group', array( 'TMDO_CLI_Post', 'post_migrate_group' ) ); WP_CLI::add_command( 'tmdo post-migrate-group', array( 'TMDO_CLI_Post', 'post_migrate_group' ) ); WP_CLI::add_command( 'wpdo post-cleanup', array( 'TMDO_CLI_Post', 'post_cleanup' ) ); WP_CLI::add_command( 'tmdo post-cleanup', array( 'TMDO_CLI_Post', 'post_cleanup' ) ); WP_CLI::add_command( 'wpdo post-cutover-legacy', array( 'TMDO_CLI_Post', 'post_cutover_legacy' ) ); WP_CLI::add_command( 'tmdo post-cutover-legacy', array( 'TMDO_CLI_Post', 'post_cutover_legacy' ) ); WP_CLI::add_command( 'wpdo cleanup-hp-transients', array( 'TMDO_CLI_Post', 'cleanup_hp_transients' ) ); WP_CLI::add_command( 'tmdo cleanup-hp-transients', array( 'TMDO_CLI_Post', 'cleanup_hp_transients' ) ); WP_CLI::add_command( 'wpdo post-benchmark', array( 'TMDO_CLI_Post', 'post_benchmark' ) ); WP_CLI::add_command( 'tmdo post-benchmark', array( 'TMDO_CLI_Post', 'post_benchmark' ) ); WP_CLI::add_command( 'wpdo post-shadow-report', array( 'TMDO_CLI_Post', 'post_shadow_report' ) ); WP_CLI::add_command( 'tmdo post-shadow-report', array( 'TMDO_CLI_Post', 'post_shadow_report' ) );