Skip to content

Instantly share code, notes, and snippets.

@seanlanglands
Created December 9, 2025 23:11
Show Gist options
  • Select an option

  • Save seanlanglands/dfe8a93b5b05b8a1e3af010654f0b9a0 to your computer and use it in GitHub Desktop.

Select an option

Save seanlanglands/dfe8a93b5b05b8a1e3af010654f0b9a0 to your computer and use it in GitHub Desktop.
<?php
/**
* Plugin Name: CLI: Bulk Assign Term by IDs
* Description: WP-CLI command to bulk-assign a taxonomy term to explicit post IDs.
*/
if ( ( defined( 'WP_CLI' ) && WP_CLI ) && class_exists( 'WPCOM_VIP_CLI_Command' ) ) {
/**
* Bulk-assign a taxonomy term to a comma-separated list of post IDs.
*
* ## OPTIONS
*
* --taxonomy=<taxonomy>
* : Taxonomy slug.
*
* --term=<term>
* : Term slug or ID to assign.
*
* --ids=<ids>
* : Comma-separated list of post IDs. Required.
*
* [--dry-run]
* : Default: yes. Shows what would happen.
*
* [--no-dry-run]
* : Actually update terms.
*
* ## EXAMPLES
*
* # Dry run (default)
* wp bulk-terms assign \
* --taxonomy=collection \
* --term=adventure \
* --ids=123,456,789
*
* # Apply changes
* wp bulk-terms assign \
* --taxonomy=collection \
* --term=adventure \
* --ids=123,456,789 \
* --no-dry-run
*/
class Bulk_Assign_Term_By_IDs_Command extends WPCOM_VIP_CLI_Command {
public function assign( $args, $assoc_args ) {
$taxonomy = $assoc_args['taxonomy'] ?? null;
$term_arg = $assoc_args['term'] ?? null;
$ids_arg = $assoc_args['ids'] ?? null;
if ( ! $taxonomy || ! $term_arg || ! $ids_arg ) {
WP_CLI::error( '--taxonomy, --term, and --ids are all required.' );
}
// Dry run default = true.
$dry_run = WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run', true );
if ( WP_CLI\Utils\get_flag_value( $assoc_args, 'no-dry-run', false ) ) {
$dry_run = false;
}
// Disable term counting and Elasticsearch indexing.
if ( ! $dry_run ) {
$this->start_bulk_operation();
}
// Validate taxonomy.
if ( ! taxonomy_exists( $taxonomy ) ) {
WP_CLI::error( "Taxonomy '$taxonomy' does not exist." );
}
// Resolve term.
if ( is_numeric( $term_arg ) ) {
$term = get_term( (int) $term_arg, $taxonomy );
} else {
$term = get_term_by( 'slug', $term_arg, $taxonomy );
}
if ( ! $term || is_wp_error( $term ) ) {
WP_CLI::error( "Could not resolve term '$term_arg' in taxonomy '$taxonomy'." );
}
$term_id = (int) $term->term_id;
// Parse the IDs list.
$ids = array_filter(
array_map( 'intval', explode( ',', $ids_arg ) ),
fn( $id ) => $id > 0
);
if ( empty( $ids ) ) {
WP_CLI::error( 'No valid post IDs were provided.' );
}
WP_CLI::log(
sprintf(
'Processing %d posts. Taxonomy: %s, Term: %s (%d), Dry-run: %s',
count( $ids ),
$taxonomy,
$term->slug,
$term_id,
$dry_run ? 'yes' : 'no'
)
);
$progress = WP_CLI\Utils\make_progress_bar(
$dry_run ? 'Simulating term assignment' : 'Assigning term',
count( $ids )
);
$updated = 0;
$unchanged = 0;
$skipped = 0;
foreach ( $ids as $post_id ) {
$progress->tick();
if ( ! get_post( $post_id ) ) {
$skipped++;
WP_CLI::warning( "Post $post_id does not exist." );
continue;
}
$current_terms = wp_get_object_terms(
$post_id,
$taxonomy,
[ 'fields' => 'ids' ]
);
if ( is_wp_error( $current_terms ) ) {
$skipped++;
WP_CLI::warning( "Post $post_id: failed getting current terms." );
continue;
}
// Already has term → unchanged.
if ( in_array( $term_id, $current_terms, true ) ) {
$unchanged++;
continue;
}
if ( $dry_run ) {
$updated++;
WP_CLI::log( "[DRY-RUN] Would assign term {$term_id} to post {$post_id}" );
continue;
}
$new_terms = array_unique( array_merge( $current_terms, [ $term_id ] ) );
$result = wp_set_object_terms( $post_id, $new_terms, $taxonomy, false );
if ( is_wp_error( $result ) ) {
$skipped++;
WP_CLI::warning( "Post $post_id: failed assigning term." );
continue;
}
$updated++;
}
$progress->finish();
// Update counts only after real runs.
if ( ! $dry_run ) {
$this->end_bulk_operation();
}
WP_CLI::success(
sprintf(
'Done. Updated: %d, Already had term: %d, Skipped: %d. Dry-run: %s',
$updated,
$unchanged,
$skipped,
$dry_run ? 'yes' : 'no'
)
);
}
}
WP_CLI::add_command( 'bulk-terms assign', [ 'Bulk_Assign_Term_By_IDs_Command', 'assign' ] );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment