Add wp term prune command to remove terms with 0 or 1 published posts#586
Add wp term prune command to remove terms with 0 or 1 published posts#586
wp term prune command to remove terms with 0 or 1 published posts#586Conversation
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
wp term prune command to remove terms with 0 or 1 published posts
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable wp term prune command for cleaning up unused or barely-used terms, which is particularly useful for large sites. The implementation is solid and is accompanied by a comprehensive set of Behat tests covering various scenarios. My review includes a single, significant suggestion to refactor the prune method. The proposed changes aim to improve performance by using a more efficient database query, enhance code readability by consolidating loops, and increase robustness by handling a potential edge case during term deletion. Overall, this is a great addition.
| foreach ( $args as $taxonomy ) { | ||
| if ( ! taxonomy_exists( $taxonomy ) ) { | ||
| WP_CLI::error( "Taxonomy {$taxonomy} doesn't exist." ); | ||
| } | ||
| } | ||
|
|
||
| $dry_run = (bool) Utils\get_flag_value( $assoc_args, 'dry-run', false ); | ||
|
|
||
| foreach ( $args as $taxonomy ) { | ||
| $terms = get_terms( | ||
| [ | ||
| 'taxonomy' => $taxonomy, | ||
| 'hide_empty' => false, | ||
| ] | ||
| ); | ||
|
|
||
| // This should never happen because of the taxonomy_exists check above. | ||
| if ( is_wp_error( $terms ) ) { | ||
| WP_CLI::warning( "Could not retrieve terms for taxonomy {$taxonomy}." ); | ||
| continue; | ||
| } | ||
|
|
||
| /** | ||
| * @var \WP_Term[] $terms | ||
| */ | ||
|
|
||
| $total = 0; | ||
| $successes = 0; | ||
| $errors = 0; | ||
|
|
||
| foreach ( $terms as $term ) { | ||
| if ( $term->count > 1 ) { | ||
| continue; | ||
| } | ||
|
|
||
| ++$total; | ||
|
|
||
| if ( $dry_run ) { | ||
| WP_CLI::log( "Would delete {$taxonomy} {$term->term_id}." ); | ||
| ++$successes; | ||
| continue; | ||
| } | ||
|
|
||
| $result = wp_delete_term( $term->term_id, $taxonomy ); | ||
|
|
||
| if ( is_wp_error( $result ) ) { | ||
| WP_CLI::warning( $result ); | ||
| ++$errors; | ||
| } elseif ( $result ) { | ||
| WP_CLI::log( "Deleted {$taxonomy} {$term->term_id}." ); | ||
| ++$successes; | ||
| } | ||
| } | ||
|
|
||
| if ( $dry_run ) { | ||
| $term_word = Utils\pluralize( 'term', $successes ); | ||
| WP_CLI::success( "{$successes} {$taxonomy} {$term_word} would be pruned." ); | ||
| } else { | ||
| Utils\report_batch_operation_results( 'term', 'prune', $total, $successes, $errors ); | ||
| } | ||
| } |
There was a problem hiding this comment.
The prune function can be refactored for better performance, readability, and robustness.
- Performance: Instead of fetching all terms with
get_terms()and then filtering them in PHP, it's more efficient to query the database directly for only the term IDs that need to be pruned (i.e., those with a count of 1 or less). This will significantly improve performance on sites with a large number of terms. - Readability: The two separate
foreachloops iterating over$argscan be combined into a single loop. You can perform thetaxonomy_exists()check at the beginning of each iteration before proceeding with the pruning logic for that taxonomy. - Robustness: The
wp_delete_term()function returnsfalseif the term to be deleted doesn't exist. This edge case (which could happen in a race condition) should be handled by issuing a warning, similar to how theterm deletecommand behaves.
Here is a suggested refactoring that applies these improvements:
global $wpdb;
$dry_run = (bool) Utils\get_flag_value( $assoc_args, 'dry-run', false );
foreach ( $args as $taxonomy ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
WP_CLI::error( "Taxonomy {$taxonomy} doesn't exist." );
}
$term_ids_to_prune = $wpdb->get_col(
$wpdb->prepare(
"SELECT term_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s AND count <= 1",
$taxonomy
)
);
$total = count( $term_ids_to_prune );
$successes = 0;
$errors = 0;
if ( $total > 0 ) {
foreach ( $term_ids_to_prune as $term_id ) {
if ( $dry_run ) {
WP_CLI::log( "Would delete {$taxonomy} {$term_id}." );
++$successes;
continue;
}
$result = wp_delete_term( $term_id, $taxonomy );
if ( is_wp_error( $result ) ) {
WP_CLI::warning( $result );
++$errors;
} elseif ( $result ) {
WP_CLI::log( "Deleted {$taxonomy} {$term_id}." );
++$successes;
} else {
WP_CLI::warning( "Term {$term_id} in taxonomy {$taxonomy} doesn't exist." );
}
}
}
if ( $dry_run ) {
$term_word = Utils\pluralize( 'term', $successes );
WP_CLI::success( "{$successes} {$taxonomy} {$term_word} would be pruned." );
} else {
Utils\report_batch_operation_results( 'term', 'prune', $total, $successes, $errors );
}
}There was a problem hiding this comment.
Pull request overview
Adds a new wp term prune subcommand to help bulk-remove low-usage terms (0–1 posts) from one or more taxonomies, with optional dry-run behavior for previewing deletions.
Changes:
- Adds
Term_Command::prune()to find and delete terms withcount <= 1, plus--dry-runsupport. - Registers
term pruneincomposer.jsonso the command is discoverable by WP-CLI. - Adds Behat coverage for prune behavior across several scenarios (0 posts, 1 post, 2+ posts, dry-run, invalid taxonomy, multi-taxonomy).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Term_Command.php |
Implements the new wp term prune subcommand and its CLI output paths (dry-run vs delete). |
features/term-prune.feature |
Adds end-to-end Behat scenarios validating prune behavior and error handling. |
composer.json |
Registers the new command in the commands list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com>
prunemethod toTerm_Commandclass insrc/Term_Command.phppruneexample"term prune"to the commands list incomposer.jsonfeatures/term-prune.featurewith Behat testsOriginal prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.