Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save boonebgorges/e873fc9589998f5b07e1 to your computer and use it in GitHub Desktop.
Save boonebgorges/e873fc9589998f5b07e1 to your computer and use it in GitHub Desktop.

Preparing Plugins for Term Splitting

Historically, two terms in different taxonomies with the same slug (for instance, a tag and a category sharing the slug "news") have shared a single term ID. Beginning in WordPress 4.2, when one of these shared terms is updated, it will be split: the updated term will be assigned a new term ID.

In the vast majority of situations, this update will be seamless and uneventful. However, some plugins and themes store term IDs in options, post meta, user meta, or elsewhere. WP 4.2 will include two different tools to help authors of these plugins and themes with the transition.

The 'split_shared_term' action

When a shared term is assigned a new term ID, a new 'split_shared_term' action is fired. Plugins and themes that store term IDs should hook to this action to perform necessary migrations. The documentation for the hook is as follows:

/**
 * Fires after a previously shared taxonomy term is split into two separate terms.
 *
 * @since 4.2.0
 *
 * @param int    $term_id          ID of the formerly shared term.
 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
 * @param string $taxonomy         Taxonomy for the split term.
 */

Here are a few examples of how plugin and theme authors can leverage this action to ensure that stored term IDs are updated.

  1. Updating a term ID stored in an option Let's say your plugin stores an option called 'featured_tags' that contains an array of term IDs (update_option( 'featured_tags', array( 4, 6, 10 ) )). In this example, we'll hook to 'split_shared_term', check whether the updated term ID is in the array, and update if necessary.

    /**
     * Update featured tags when a term gets split.
     * 
     * @param int    $term_id          ID of the formerly shared term.
     * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
     * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
     * @param string $taxonomy         Taxonomy for the split term.
     */
    function my_featured_tags_split_shared_term( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
    	// We only care about tags, so we'll first verify that the term is a tag.
    	if ( 'post_tag' == $taxonomy ) {
    		// Get the current featured tags.
    		$featured_tags = get_option( 'featured_tags' );
    
    		// If the updated term is in the array, note the array key.
    		$found_term = array_search( $term_id, $featured_tags );
    		if ( false !== $found_term ) {
    			// The updated term is a featured tag! Replace it in the array, and resave. 
    			$featured_tags[ $found_term ] = $new_term_id;
    			update_option( 'featured_tags', $featured_tags );
    		}
    	}
    }
    add_action( 'split_shared_term', 'my_featured_tags_split_shared_term', 10, 4 );
  2. Updating a term ID stored in post meta Sometimes a plugin might store term ids in post meta. In this case, we'll use a get_posts() query to locate posts with the meta key "primary_category" and a meta value matching the split term ID. Once we've identified the posts, we'll use update_post_meta() to change the values.

    /**
     * Check primary categories when a term gets split to see if any of them
     * need to be updated.
     *
     * @param int    $term_id          ID of the formerly shared term.
     * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
     * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
     * @param string $taxonomy         Taxonomy for the split term.
     */
    function my_primary_category_split_shared_term( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
    	// Ignore all updates except those to categories
    	if ( 'category' == $taxonomy ) {
    		// Find all the posts where the primary category matches the old term ID.
    		$post_ids = get_posts( array(
    			'fields'     => 'ids',
    			'meta_key'   => 'primary_category',
    			'meta_value' => $term_id,
    		) );
    
    		// If we found posts, update the term ID stored in post meta.
    		if ( $post_ids ) {
    			foreach ( $post_ids as $post_id ) {
    				update_post_meta( $post_id, 'primary_category', $new_term_id, $term_id );
    			}
    		}
    	}
    }
    add_action( 'split_shared_term', 'my_primary_category_split_shared_term', 10, 4 );

The wp_get_split_term() function

'split_shared_term' is the preferred method for processing term ID changes. However, there may be cases - such as a delayed plugin update - where terms are split, without your plugin getting a chance to hook to the 'split_shared_term' action. WP 4.2 stores information about taxonomy terms that have been split, and provides the wp_get_split_term() utility function to retrieve this information.

Consider the case above, where your plugin stores term IDs in an option called 'featured_tags'. You may want to build a function that validates these tag IDs (perhaps to be run on plugin update), to be sure that none of the featured tags has been split:

function my_featured_tags_check_for_split_terms() {
	$featured_tag_ids = get_option( 'featured_tags', array() );

	// Check to see whether any IDs correspond to post_tag terms that have been split.
	foreach ( $featured_tag_ids as $index => $featured_tag_id ) {
		$new_term_id = wp_get_split_term( $featured_tag_id, 'post_tag' );

		if ( $new_term_id ) {
			$featured_tag_ids[ $index ] = $new_term_id;
		}
	}

	// Resave.
	update_option( 'featured_tags', $featured_tag_ids );
}

Note that wp_get_split_term() takes two parameters - $old_term_id and $taxonomy - and returns an integer. If you need to retrieve a list of all split terms associated with an old term ID, regardless of taxonomy, use wp_get_split_terms( $old_term_id ).

@markjaquith
Copy link

wp_get_split_terms() example Markdown barfed a bit. I think the opening code fence is indented.

Also, is this safe to run multiple times? No new term ID is going to end up being the same as an old one, or anything weird like that, right?

@boonebgorges
Copy link
Author

@markjaquith - Gah, apparently you don't get email notification of Gist comments. Sorry I missed this last week.

I think the Markdown is fixed now.

Regarding multiple runs:

  • Devs should never call _split_shared_term() directly, and it's not mentioned in any of the docs. That being said, it's idempotent in the sense that it will bail if it finds that there's nothing to split, as will be the case in a previously split term. Old term IDs are not deleted, and new term IDs come from INSERT statements, so they'll always be incremented and minty fresh.
  • The 'split_shared_term' action will run every time a term is split, so it's up to the developer to check the $term_id and $taxonomy as necessary to determine whether action is required on each instance.
  • wp_get_split_terms() just fetches stuff from the db, so there's no risk of dupes there or anything.

So no, I think we're good as far as multiple runs go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment