... * : List terms of one or more taxonomies * * [--=] * : Filter by one or more fields (see get_terms() $args parameter for a list of fields). * * [--field=] * : Prints the value of a single field for each term. * * [--fields=] * : Limit the output to specific object fields. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - ids * - json * - count * - yaml * --- * * ## AVAILABLE FIELDS * * These fields will be displayed by default for each term: * * * term_id * * term_taxonomy_id * * name * * slug * * description * * parent * * count * * These fields are optionally available: * * * url * * ## EXAMPLES * * # List post categories * $ wp term list category --format=csv * term_id,term_taxonomy_id,name,slug,description,parent,count * 2,2,aciform,aciform,,0,1 * 3,3,antiquarianism,antiquarianism,,0,1 * 4,4,arrangement,arrangement,,0,1 * 5,5,asmodeus,asmodeus,,0,1 * * # List post tags * $ wp term list post_tag --fields=name,slug * +-----------+-------------+ * | name | slug | * +-----------+-------------+ * | 8BIT | 8bit | * | alignment | alignment-2 | * | Articles | articles | * | aside | aside | * +-----------+-------------+ * * @subcommand list */ public function list_( $args, $assoc_args ) { foreach ( $args as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { WP_CLI::error( "Taxonomy {$taxonomy} doesn't exist." ); } } $formatter = $this->get_formatter( $assoc_args ); $defaults = [ 'hide_empty' => false, ]; $assoc_args = array_merge( $defaults, $assoc_args ); if ( ! empty( $assoc_args['term_id'] ) ) { $term = get_term_by( 'id', $assoc_args['term_id'], $args[0] ); $terms = [ $term ]; } elseif ( ! empty( $assoc_args['include'] ) && ! empty( $assoc_args['orderby'] ) && 'include' === $assoc_args['orderby'] && Utils\wp_version_compare( '4.7', '<' ) ) { $terms = []; $term_ids = explode( ',', $assoc_args['include'] ); foreach ( $term_ids as $term_id ) { $term = get_term_by( 'id', $term_id, $args[0] ); if ( $term && ! is_wp_error( $term ) ) { $terms[] = $term; } else { WP_CLI::warning( "Invalid term {$term_id}." ); } } } else { // phpcs:ignore WordPress.WP.DeprecatedParameters.Get_termsParam2Found -- Required for backward compatibility. $terms = get_terms( $args, $assoc_args ); } $terms = array_map( function ( $term ) { $term->count = (int) $term->count; $term->parent = (int) $term->parent; $term->url = get_term_link( $term ); return $term; }, $terms ); if ( 'ids' === $formatter->format ) { $terms = wp_list_pluck( $terms, 'term_id' ); echo implode( ' ', $terms ); } else { $formatter->display_items( $terms ); } } /** * Creates a new term. * * ## OPTIONS * * * : Taxonomy for the new term. * * * : A name for the new term. * * [--slug=] * : A unique slug for the new term. Defaults to sanitized version of name. * * [--description=] * : A description for the new term. * * [--parent=] * : A parent for the new term. * * [--porcelain] * : Output just the new term id. * * ## EXAMPLES * * # Create a new category "Apple" with a description. * $ wp term create category Apple --description="A type of fruit" * Success: Created category 199. */ public function create( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; $defaults = [ 'slug' => sanitize_title( $term ), 'description' => '', 'parent' => 0, ]; $assoc_args = wp_parse_args( $assoc_args, $defaults ); $porcelain = Utils\get_flag_value( $assoc_args, 'porcelain' ); unset( $assoc_args['porcelain'] ); // Compatibility for < WP 4.0 if ( $assoc_args['parent'] > 0 && ! term_exists( (int) $assoc_args['parent'] ) ) { WP_CLI::error( 'Parent term does not exist.' ); } $assoc_args = wp_slash( $assoc_args ); $term = wp_slash( $term ); $result = wp_insert_term( $term, $taxonomy, $assoc_args ); if ( is_wp_error( $result ) ) { WP_CLI::error( $result->get_error_message() ); } elseif ( $porcelain ) { WP_CLI::line( $result['term_id'] ); } else { WP_CLI::success( "Created {$taxonomy} {$result['term_id']}." ); } } /** * Gets details about a term. * * ## OPTIONS * * * : Taxonomy of the term to get * * * : ID or slug of the term to get * * [--by=] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * [--field=] * : Instead of returning the whole term, returns the value of a single field. * * [--fields=] * : Limit the output to specific fields. Defaults to all fields. * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - csv * - json * - yaml * --- * * ## EXAMPLES * * # Get details about a category with id 199. * $ wp term get category 199 --format=json * {"term_id":199,"name":"Apple","slug":"apple","term_group":0,"term_taxonomy_id":199,"taxonomy":"category","description":"A type of fruit","parent":0,"count":0,"filter":"raw"} * * # Get details about a category with slug apple. * $ wp term get category apple --by=slug --format=json * {"term_id":199,"name":"Apple","slug":"apple","term_group":0,"term_taxonomy_id":199,"taxonomy":"category","description":"A type of fruit","parent":0,"count":0,"filter":"raw"} */ public function get( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term, $taxonomy ); if ( ! $term ) { WP_CLI::error( "Term doesn't exist." ); } if ( ! isset( $term->url ) ) { $term->url = get_term_link( $term ); } if ( empty( $assoc_args['fields'] ) ) { $term_array = get_object_vars( $term ); $assoc_args['fields'] = array_keys( $term_array ); } $term->count = (int) $term->count; $term->parent = (int) $term->parent; $formatter = $this->get_formatter( $assoc_args ); $formatter->display_item( $term ); } /** * Updates an existing term. * * ## OPTIONS * * * : Taxonomy of the term to update. * * * : ID or slug for the term to update. * * [--by=] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * [--name=] * : A new name for the term. * * [--slug=] * : A new slug for the term. * * [--description=] * : A new description for the term. * * [--parent=] * : A new parent for the term. * * ## EXAMPLES * * # Change category with id 15 to use the name "Apple" * $ wp term update category 15 --name=Apple * Success: Term updated. * * # Change category with slug apple to use the name "Apple" * $ wp term update category apple --by=slug --name=Apple * Success: Term updated. */ public function update( $args, $assoc_args ) { list( $taxonomy, $term ) = $args; $defaults = [ 'name' => null, 'slug' => null, 'description' => null, 'parent' => null, ]; $assoc_args = wp_parse_args( $assoc_args, $defaults ); foreach ( $assoc_args as $key => $value ) { if ( null === $value ) { unset( $assoc_args[ $key ] ); } } $assoc_args = wp_slash( $assoc_args ); $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term, $taxonomy ); if ( ! $term ) { WP_CLI::error( "Term doesn't exist." ); } // Update term. $result = wp_update_term( $term->term_id, $taxonomy, $assoc_args ); if ( is_wp_error( $result ) ) { WP_CLI::error( $result->get_error_message() ); } else { WP_CLI::success( 'Term updated.' ); } } /** * Deletes an existing term. * * Errors if the term doesn't exist, or there was a problem in deleting it. * * ## OPTIONS * * * : Taxonomy of the term to delete. * * ... * : One or more IDs or slugs of terms to delete. * * [--by=] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * ## EXAMPLES * * # Delete post category by id * $ wp term delete category 15 * Deleted category 15. * Success: Deleted 1 of 1 terms. * * # Delete post category by slug * $ wp term delete category apple --by=slug * Deleted category 15. * Success: Deleted 1 of 1 terms. * * # Delete all post tags * $ wp term list post_tag --field=term_id | xargs wp term delete post_tag * Deleted post_tag 159. * Deleted post_tag 160. * Deleted post_tag 161. * Success: Deleted 3 of 3 terms. */ public function delete( $args, $assoc_args ) { $taxonomy = array_shift( $args ); $successes = 0; $errors = 0; foreach ( $args as $term_id ) { $term = $term_id; // Get term by specified argument otherwise get term by id. $field = Utils\get_flag_value( $assoc_args, 'by' ); if ( 'slug' === $field ) { // Get term by slug. $term = get_term_by( $field, $term, $taxonomy ); // If term not found, then show error message and skip the iteration. if ( ! $term ) { WP_CLI::warning( "{$taxonomy} {$term_id} doesn't exist." ); continue; } // Get the term id; $term_id = $term->term_id; } $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( "{$taxonomy} {$term_id} doesn't exist." ); } } Utils\report_batch_operation_results( 'term', 'delete', count( $args ), $successes, $errors ); } /** * Generates some terms. * * Creates a specified number of new terms with dummy data. * * ## OPTIONS * * * : The taxonomy for the generated terms. * * [--count=] * : How many terms to generate? * --- * default: 100 * --- * * [--max_depth=] * : Generate child terms down to a certain depth. * --- * default: 1 * --- * * [--format=] * : Render output in a particular format. * --- * default: progress * options: * - progress * - ids * --- * * ## EXAMPLES * * # Generate post categories. * $ wp term generate category --count=10 * Generating terms 100% [=========] 0:02 / 0:02 * * # Add meta to every generated term. * $ wp term generate category --format=ids --count=3 | xargs -d ' ' -I % wp term meta add % foo bar * Success: Added custom field. * Success: Added custom field. * Success: Added custom field. */ public function generate( $args, $assoc_args ) { global $wpdb; list ( $taxonomy ) = $args; $defaults = [ 'count' => 100, 'max_depth' => 1, ]; $final_args = array_merge( $defaults, $assoc_args ); $count = $final_args['count']; $max_depth = $final_args['max_depth']; if ( ! taxonomy_exists( $taxonomy ) ) { WP_CLI::error( "'{$taxonomy}' is not a registered taxonomy." ); } $label = get_taxonomy( $taxonomy )->labels->singular_name; $slug = sanitize_title_with_dashes( $label ); $hierarchical = get_taxonomy( $taxonomy )->hierarchical; $format = Utils\get_flag_value( $assoc_args, 'format', 'progress' ); $notify = false; if ( 'progress' === $format ) { $notify = Utils\make_progress_bar( 'Generating terms', $count ); } $previous_term_id = 0; $current_parent = 0; $current_depth = 1; $max_id = (int) $wpdb->get_var( "SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} ORDER BY term_taxonomy_id DESC LIMIT 1" ); $suspend_cache_invalidation = wp_suspend_cache_invalidation( true ); $created = []; for ( $index = $max_id + 1; $index <= $max_id + $count; $index++ ) { if ( $hierarchical ) { if ( $previous_term_id && $this->maybe_make_child() && $current_depth < $max_depth ) { $current_parent = $previous_term_id; ++$current_depth; } elseif ( $this->maybe_reset_depth() ) { $current_parent = 0; $current_depth = 1; } } $args = [ 'parent' => $current_parent, 'slug' => $slug . "-{$index}", ]; $name = "{$label} {$index}"; $term = wp_insert_term( $name, $taxonomy, $args ); if ( is_wp_error( $term ) ) { WP_CLI::warning( $term ); } else { $created[] = $term['term_id']; $previous_term_id = $term['term_id']; if ( 'ids' === $format ) { echo $term['term_id']; if ( $index < $max_id + $count ) { echo ' '; } } } if ( 'progress' === $format ) { $notify->tick(); } } wp_suspend_cache_invalidation( $suspend_cache_invalidation ); clean_term_cache( $created, $taxonomy ); if ( 'progress' === $format ) { $notify->finish(); } } /** * Recalculates number of posts assigned to each term. * * In instances where manual updates are made to the terms assigned to * posts in the database, the number of posts associated with a term * can become out-of-sync with the actual number of posts. * * This command runs wp_update_term_count() on the taxonomy's terms * to bring the count back to the correct value. * * ## OPTIONS * * ... * : One or more taxonomies to recalculate. * * ## EXAMPLES * * # Recount posts assigned to each categories and tags * $ wp term recount category post_tag * Success: Updated category term count. * Success: Updated post_tag term count. * * # Recount all listed taxonomies * $ wp taxonomy list --field=name | xargs wp term recount * Success: Updated category term count. * Success: Updated post_tag term count. * Success: Updated nav_menu term count. * Success: Updated link_category term count. * Success: Updated post_format term count. */ public function recount( $args ) { foreach ( $args as $taxonomy ) { if ( ! taxonomy_exists( $taxonomy ) ) { WP_CLI::warning( "Taxonomy {$taxonomy} does not exist." ); } else { // phpcs:ignore WordPress.WP.DeprecatedParameters.Get_termsParam2Found -- Required for backward compatibility. $terms = get_terms( $taxonomy, [ 'hide_empty' => false ] ); $term_taxonomy_ids = wp_list_pluck( $terms, 'term_taxonomy_id' ); wp_update_term_count( $term_taxonomy_ids, $taxonomy ); WP_CLI::success( "Updated {$taxonomy} term count." ); } } } /** * Migrate a term of a taxonomy to another taxonomy. * * ## OPTIONS * * * : Slug or ID of the term to migrate. * * [--by=] * : Explicitly handle the term value as a slug or id. * --- * default: id * options: * - slug * - id * --- * * [--from=] * : Taxonomy slug of the term to migrate. * * [--to=] * : Taxonomy slug to migrate to. * * ## EXAMPLES * * # Migrate a category's term (video) to tag taxonomy. * $ wp term migrate 9190 --from=category --to=post_tag * Term 'video' assigned to post 1155. * Term 'video' migrated. * Old instance of term 'video' removed from its original taxonomy. * Success: Migrated the term 'video' from taxonomy 'category' to taxonomy 'post_tag' for 1 post. */ public function migrate( $args, $assoc_args ) { $term_reference = $args[0]; $original_taxonomy = Utils\get_flag_value( $assoc_args, 'from' ); $destination_taxonomy = Utils\get_flag_value( $assoc_args, 'to' ); $term = get_term_by( Utils\get_flag_value( $assoc_args, 'by' ), $term_reference, $original_taxonomy ); if ( ! $term ) { WP_CLI::error( "Taxonomy term '{$term_reference}' for taxonomy '{$original_taxonomy}' doesn't exist." ); } $original_taxonomy = get_taxonomy( $original_taxonomy ); $id = wp_insert_term( $term->name, $destination_taxonomy, [ 'slug' => $term->slug, 'parent' => 0, 'description' => $term->description, ] ); if ( is_wp_error( $id ) ) { WP_CLI::error( $id->get_error_message() ); } $post_ids = get_objects_in_term( $term->term_id, $original_taxonomy->name ); foreach ( $post_ids as $post_id ) { $type = get_post_type( $post_id ); if ( in_array( $type, $original_taxonomy->object_type, true ) ) { $term_taxonomy_id = wp_set_object_terms( $post_id, $id['term_id'], $destination_taxonomy, true ); if ( is_wp_error( $term_taxonomy_id ) ) { WP_CLI::error( "Failed to assign the term '{$term->slug}' to the post {$post_id}. Reason: " . $term_taxonomy_id->get_error_message() ); } WP_CLI::log( "Term '{$term->slug}' assigned to post {$post_id}." ); } clean_post_cache( $post_id ); } clean_term_cache( $term->term_id ); WP_CLI::log( "Term '{$term->slug}' migrated." ); $del = wp_delete_term( $term->term_id, $original_taxonomy->name ); if ( is_wp_error( $del ) ) { WP_CLI::error( "Failed to delete the term '{$term->slug}'. Reason: " . $del->get_error_message() ); } WP_CLI::log( "Old instance of term '{$term->slug}' removed from its original taxonomy." ); $post_count = count( $post_ids ); $post_plural = Utils\pluralize( 'post', $post_count ); WP_CLI::success( "Migrated the term '{$term->slug}' from taxonomy '{$original_taxonomy->name}' to taxonomy '{$destination_taxonomy}' for {$post_count} {$post_plural}." ); } private function maybe_make_child() { // 50% chance of making child term. return ( wp_rand( 1, 2 ) === 1 ); } private function maybe_reset_depth() { // 10% chance of resetting to root depth. return ( wp_rand( 1, 10 ) === 7 ); } private function get_formatter( &$assoc_args ) { return new Formatter( $assoc_args, $this->fields, 'term' ); } }