BrennaOBrien

Auto-populating WordPress Taxonomies with Custom Post Entries

A better way of linking custom post types.

I've always found linking between post types to be clunky. Sure, it's simple enough to create the link using plugins like Advanced Custom Fields or Posts to Posts, but querying by relationship data within templates can get messy.

For example, when I wanted a count of all post-type-A entries that had a relationship to a particular post-type-B entry, I eneded up having to write a beast of a custom SQL query. Not exactly harnessing the power of WordPress, amirite?

Compare that to the way taxonomies natively work. If I want to know how many post-type-A entries have been tagged with taxonomy term category-1 it's a simple matter of:

$term = get_term_by('slug', 'category-1', 'my-taxonomy');
$count = $term->count;

Wouldn't it be great to have a custom taxonomy on post-type-A auto-populate with the entries from post-type-B? Turns out that's pretty easy.

The Plan

In your theme's functions file, we can use register_taxonomy, wp_insert_term, wp_update_term, and the save_post action to setup a new custom taxonomy, then assign or update terms whenever a custom post gets updated.

Step 1: Create a Custom Taxonomy for post-type-A

This is pretty standard if you've created a custom taxonomy before. The only notable exception is that we set some restricted capabilities to prevent non-admin users from editing the auto-generated terms. Attach it to the custom post type that you want to be tagged.

function custom_tax_init(){

  //set some options for our new custom taxonomy
  $args = array(
    'label' => __( 'My Custom Taxonomy' ),
    'hierarchical' => true,
    'capabilities' => array(
      // allow anyone editing posts to assign terms
      'assign_terms' => 'edit_posts',
      /* 
      * but you probably don't want anyone except 
      * admins messing with what gets auto-generated! 
      */
      'edit_terms' => 'administrator'
    )
  );

  /* 
  * create the custom taxonomy and attach it to
  * custom post type A 
  */
  register_taxonomy( 'my-taxonomy', 'post-type-A', $args);
}

add_action( 'init', 'custom_tax_init' );

Step 2: Populate terms based on post-type-B

We'll use the wordpress save_post action to trigger an update function whenever a post-type-B post is created or edited. Note that save_post is a catch all for any kind of update, for any post type, so we'll need to: * make sure we're dealing with a post-type-B post * determine if a post is compltely new or just being edited

We map the following data: * post title -> term name * post slug -> term slug * post ID -> term description (as a unique link)

function update_custom_terms($post_id) {

  // only update terms if it's a post-type-B post
  if ( 'post-type-B' != get_post_type($post_id)) {
    return;
  }

  // don't create or update terms for system generated posts
  if (get_post_status($post_id) == 'auto-draft') {
    return;
  }
    
  /*
  * Grab the post title and slug to use as the new 
  * or updated term name and slug
  */
  $term_title = get_the_title($post_id);
  $term_slug = get_post( $post_id )->post_name;

  /*
  * Check if a corresponding term already exists by comparing 
  * the post ID to all existing term descriptions. 
  */
  $existing_terms = get_terms('my-taxonomy', array(
    'hide_empty' => false
    )
  );

  foreach($existing_terms as $term) {
    if ($term->description == $post_id) {
      //term already exists, so update it and we're done
      wp_update_term($term->term_id, 'my-taxonomy', array(
        'name' => $term_title,
        'slug' => $term_slug
        )
      );
      return;
    }
  }

  /* 
  * If we didn't find a match above, this is a new post, 
  * so create a new term.
  */
  wp_insert_term($term_title, 'my-taxonomy', array(
    'slug' => $term_slug,
    'description' => $post_id
    )
  );
}

//run the update function whenever a post is created or edited
add_action('save_post', 'update_custom_terms');

You can also run these functions multiple times if you need more than one relationship of this kind.

Grab both snippets as a gist here.