A refresher on custom taxonomies

When I wrote about custom taxonomies just a year ago, not much was known or understood about them in the WordPress community. For most people, anything beyond “tags” and “categories” was unneeded anyway. And, only having only taxonomies for blog posts was essentially useless.

However, with WordPress 3.0 around the corner and the introduction of custom post types, the usefulness of custom taxonomies will increase greatly.

Post types are your forms of content. Taxonomies are the glue that holds that content together, linking it in all kinds of neat ways.

As I delve deeper into the subject of post types and taxonomies in this series of posts, I’ll give more examples of how these two things are related and how they can be used to create any type of site you want with WordPress.

First, we need a primer on taxonomies.

In WordPress 3.0, taxonomies will be super-charged. While I do encourage you to go back and read my original post on custom taxonomies and my followup on how I created a movie database to gain a better understanding of taxonomies, I’ll try to cover the important details in this post.

A refresher on what taxonomies are

Taxonomies are a method for grouping content. They’re a way of intelligently creating an organizational system for data. Some examples include:

  • Finding actors (taxonomy) that have played in specific movies (content).
  • Listing musicians (taxonomy) that have performed on various albums (content).
  • Locating restaurants (content) that serve specific types of food (taxonomy).
  • Getting recipes (content) that use certain ingredients (taxonomy).
  • Searching for jobs (content) in different fields (taxonomy).
  • Visiting a forum (taxonomy) that lists forum topics (content).
  • Scanning for books (content) written by your favorite author (taxonomy).

Grouping content in this way simply isn’t possible with standard tags and categories. To understand the benefits of taxonomies, you have to open your mind to possibilities beyond a standard blog.

What’s new with taxonomies in 3.0?

Some things have changed since the major overhaul in WordPress 2.8 of taxonomies. Don’t worry; your previously-created taxonomies will still work fine. But, now you have much more control.

The biggest changes are:

  • Ability to create taxonomies for any post type (not just posts).
  • Hierarchical taxonomies now get admin screens just like non-hierarchical ones.
  • Control over the capabilities for editing, creating, deleting, and assigning terms.
  • Control over admin text for making the UI not seem so weird.

The register_taxonomy function

To register a taxonomy, we must use the register_taxonomy() WordPress function. It takes in three parameters.

register_taxonomy( $taxonomy, $objects, $arguments )

$taxonomy

The $taxonomy parameter should be a unique name for your taxonomy that’s not used by any other taxonomy.

register_taxonomy( 'division', $objects, $arguments )

$objects

The $objects parameter can be a string (single object type) or an array (multiple object types). This represents the post types that you want to register your taxonomy for.

register_taxonomy( $taxonomy, array( 'post', 'page' ), $arguments )

$arguments

The $arguments parameter is an array of arguments that help define how your taxonomy should function. I’ll cover these in more depth later.

register_taxonomy( $taxonomy, $objects, array() )

Creating a custom taxonomy

We’re going to keep everything pretty basic for this tutorial. Let’s create a taxonomy called division for your blog posts. The easiest way to do this is to add this code to your theme’s functions.php file or a plugin file.

add_action( 'init', 'register_my_taxonomies', 0 );

function register_my_taxonomies() {

	register_taxonomy(
		'division',
		array( 'post' ),
		array(
			'public' => true,
			'labels' => array(
				'name' => __( 'Divisions' ),
				'singular_name' => __( 'Division' )
			),
		)
	);
}

This will create a new item called “Divisions” for you under your “Posts” menu item in the admin.

Taxonomy screen in WordPress

Controlling your taxonomy

The arguments array (third parameter) of register_taxonomy() is the parameter that gives you fine-grain control over how your taxonomy functions in WordPress. It allows for about 10 arguments, which I’ll cover in detail here. In each section, I’ll give a basic code example of how each is used.

public

The public argument is sort of a catchall argument that controls whether your taxonomy is publicly used on the site or if works behind the scenes. Depending on whether it’s set to true or false, it’ll automatically set other arguments if they aren’t defined.

  • show_ui: Whether the taxonomy should be shown in the administration interface.
  • show_tagcloud: Whether to allow the taxonomy to be chosen in the tag cloud widget.
  • show_in_nav_menus: Whether to allow terms from the taxonomy in navigation menus.
'public' => true,
'show_ui' => true,
'show_tagcloud' => true,
'show_in_nav_menus' => true,

hierarchical

The hierarchical argument decides if your taxonomy’s terms are in a non-hierarchical (flat) or hierarchical format. By default, this is set to false, so the taxonomy will behave like the post tag taxonomy. Hierarchical taxonomies will behave like the category taxonomy.

'hierarchical' => false,

capabilities

New in WordPress 3.0 is the ability to change the capabilities for who can use the taxonomy in the admin. By default, users of roles with the manage_categories capability can manage, edit, and delete terms, and those of roles with the edit_posts capability can assign terms to a “post.”

  • manage_terms: Ability to view terms in the administration. I don’t see any other use for this.
  • edit_terms: Grants the ability to edit and create terms.
  • delete_terms: Gives permission to delete terms from the taxonomy.
  • assign_terms: Capability for assigning terms in the new/edit post screen.

If you’re using a role management plugin like Members, you can assign which users have control over your terms. If not, I suggest not changing these capabilities.

'capabilities' => array(
	'manage_terms' => 'manage_divisions',
	'edit_terms' => 'edit_divisions',
	'delete_terms' => 'delete_divisions',
	'assign_terms' => 'assign_divisions'
),

labels

The labels array is a set of text strings that control how your taxonomy is viewed in the admin. If your taxonomy is non-hierarchical, this defaults to the post tag wording. If it is hierarchical, it defaults to the category terminology.

  • name: The plural name that represents your taxonomy.
  • singular_name: The singular name for your taxonomy.
  • search_items: Text for the search button when searching the taxonomy terms.
  • popular_items: Header for the popular cloud of terms (not used with hierarchical taxonomies).
  • all_items: Text to display all of the terms of the taxonomy.
  • parent_item: Text for displaying the parent term (not used with non-hierarchical taxonomies).
  • parent_item_colon: Text for displaying the parent term followed by a colon (not used with non-hierarchical taxonomies).
  • edit_item: Represents the text when editing a term.
  • update_item: Displayed when updating a term.
  • add_new_item: Text to display for adding a new term.
  • new_item_name: Text to display for adding a new term name.
'labels' => array(
	'name' => __( 'Divisions' ),
	'singular_name' => __( 'Division' ),
	'search_items' => __( 'Search Divisions' ),
	'popular_items' => __( 'Popular Divisions' ),
	'all_items' => __( 'All Divisions' ),
	'parent_item' => __( 'Parent Division' ),
	'parent_item_colon' => __( 'Parent Division:' ),
	'edit_item' => __( 'Edit Divison' ),
	'update_item' => __( 'Update Division' ),
	'add_new_item' => __( 'Add New Division' ),
	'new_item_name' => __( 'New Division Name' ),
),

query_var

The query_var argument allows you to get posts based on particular terms of the taxonomy through a query string in the browser URL bar or through a WordPress function like query_posts() or class like WP_Query. This will default to the name of your taxonomy, but you can also set it to false to prevent queries.

'query_var' => 'division',

rewrite

The rewrite argument gives you control over the permalink structure of your taxonomy’s term archives. You may want a structure like yoursite.com/divisions/blue-group, and this argument will give you control over that. It can be set to true, false, or an array of values. The values of the array are:

  • slug: The slug you want to prefix your term archives with.
  • with_front: Whether your term archives should use the front base from your permalinks settings.
'rewrite' => array( 'slug' => 'divisions', 'with_front' => false ),

update_count_callback

update_count_callback allows you to write a custom function that will be called when the term count is updated. Most users shouldn’t need this and shouldn’t bother setting this.

'update_count_callback' => 'my_custom_count_callback',

To answer the 18 billion questions from the last year

Since I first published an article on taxonomies last year, I’ve gotten numerous questions on using them. The two questions that crop up the most:

  • Can I create a hierarchical taxonomy like categories?
  • Can I use my taxonomy with pages (or other post types)?

It’s been a long time coming, but I can finally say, “Yes! And. Yes!”

I didn’t want to overwhelm everyone with too much information in this tutorial. I mostly wanted to give a refresher on taxonomies and cover some of the new features. I encourage you to read my previous posts on the subject for a deeper understanding of taxonomies.

The problem I see now is that there’s one more question that needs answering — how to merge taxonomies and post types. I’ve touched on this a bit in this post, but you can expect a real-world example from me in the near future.

http://justintadlock.com/archives/2010/06/10/a-refresher-on-custom-taxonomies

Custom user taxonomies in WordPress

If you’re at all familiar with taxonomies in WordPress, you already know how awesome it is to add custom taxonomies to your posts or custom post types. WordPress developers have known this for a while.

What many people don’t know is that the current taxonomy schema was added way back in WordPress 2.3. Yes, that means the ability to create and use custom taxonomies has been around since 2007. We didn’t get all the cool functions added until 2.8 though.

However, one thing we’ve had since 2.3 was the ability to create taxonomies for any object type, not just posts. In WordPress, there are several object types:

  • Posts
  • Users
  • Comments
  • Links

So, you can technically create a taxonomy for any object type. Most of WordPress core support is for posts, but the API is extremely well thought out and can handle the other object types with minimal code effort.

This tutorial will focus on registering and using a taxonomy on the user object type. It will not be a 100% solution for everything you can do with a custom user taxonomy. Consider this an extremely rough draft of what’s possible.

Because this is such an in-depth topic, I cannot explain every minute detail. That would make for about a 50-page tutorial. You’ll need to be familiar with a few key areas in WordPress development before proceeding: plugins, themes, users, and taxonomies.

Registering a user taxonomy

In this tutorial, you will use and register a taxonomy called “Profession.” This is just an example, so feel free to experiment and create your own taxonomies for your uses.

To register the user taxonomy, you use the register_taxonomy() function just like you would with any other taxonomy. The following code block will register the profession taxonomy and set up its arguments.

/**
 * Registers the 'profession' taxonomy for users.  This is a taxonomy for the 'user' object type rather than a
 * post being the object type.
 */
function my_register_user_taxonomy() {

	 register_taxonomy(
		'profession',
		'user',
		array(
			'public' => true,
			'labels' => array(
				'name' => __( 'Professions' ),
				'singular_name' => __( 'Profession' ),
				'menu_name' => __( 'Professions' ),
				'search_items' => __( 'Search Professions' ),
				'popular_items' => __( 'Popular Professions' ),
				'all_items' => __( 'All Professions' ),
				'edit_item' => __( 'Edit Profession' ),
				'update_item' => __( 'Update Profession' ),
				'add_new_item' => __( 'Add New Profession' ),
				'new_item_name' => __( 'New Profession Name' ),
				'separate_items_with_commas' => __( 'Separate professions with commas' ),
				'add_or_remove_items' => __( 'Add or remove professions' ),
				'choose_from_most_used' => __( 'Choose from the most popular professions' ),
			),
			'rewrite' => array(
				'with_front' => true,
				'slug' => 'author/profession' // Use 'author' (default WP user slug).
			),
			'capabilities' => array(
				'manage_terms' => 'edit_users', // Using 'edit_users' cap to keep this simple.
				'edit_terms'   => 'edit_users',
				'delete_terms' => 'edit_users',
				'assign_terms' => 'read',
			),
			'update_count_callback' => 'my_update_profession_count' // Use a custom function to update the count.
		)
	);
}

I won’t cover all the arguments used in the code above. I’ve written about those in a previous tutorial. However, there are a few arguments you should pay careful attention to:

  • rewrite[‘slug’]: I used author/profession so that the slug would fall in line with the WordPress user slug, author. We’ll need a slight fix for this too, which I’ll get to later in the tutorial.
  • capabilities: For simplicity, I’ve set most of the capabilities to edit_users. Only administrators can manage, edit, and delete professions with this by default. The assign_terms capability is set to read so all users can edit this on their profile page. You’ll want to set up some custom capabilities or configure this to do what you want.
  • update_count_callback: You must use a custom function for this because WordPress expects the taxonomy to be for the post object type.

Once you’ve registered your taxonomy, WordPress really doesn’t do much for you. It won’t add any custom admin pages, meta boxes, or anything of the sort like it does for posts. That’ll be left up to you.

Custom term update count callback

Right off the bat, you’ll already need some custom code. Since WordPress will only update the term counts for taxonomies on posts, you’ll need a function to do this for users.

When you registered your taxonomy, you set the update_count_callback argument to my_update_profession_count. The following code is that callback function.

/**
 * Function for updating the 'profession' taxonomy count.  What this does is update the count of a specific term
 * by the number of users that have been given the term.  We're not doing any checks for users specifically here.
 * We're just updating the count with no specifics for simplicity.
 *
 * See the _update_post_term_count() function in WordPress for more info.
 *
 * @param array $terms List of Term taxonomy IDs
 * @param object $taxonomy Current taxonomy object of terms
 */
function my_update_profession_count( $terms, $taxonomy ) {
	global $wpdb;

	foreach ( (array) $terms as $term ) {

		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );

		do_action( 'edit_term_taxonomy', $term, $taxonomy );
		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
		do_action( 'edited_term_taxonomy', $term, $taxonomy );
	}
}

I just kept this extremely simple and modified the _update_post_term_count() WordPress function. Feel free to further modify it to suit your needs.

Creating the manage terms page

Manage user taxonomy terms admin page

Since WordPress does not create an admin page for managing terms of custom user taxonomies, you’ll need to create that. For the sake of simplicity and laziness, I just used the admin page WordPress already uses for other taxonomies.

The problem with this is that WordPress doesn’t recognize that the “Professions” admin page is a sub-menu of the “Users” admin page when viewing it. The sub-menu is placed correctly, but when clicking on it, the “Posts” menu is opened in the admin. I’d really love to see some WordPress core support for this.

Update: See the comment by James below to get a fix for this.

The following code is what I used, but you can definitely create a custom admin page for managing your terms.

/* Adds the taxonomy page in the admin. */
add_action( 'admin_menu', 'my_add_profession_admin_page' );

/**
 * Creates the admin page for the 'profession' taxonomy under the 'Users' menu.  It works the same as any
 * other taxonomy page in the admin.  However, this is kind of hacky and is meant as a quick solution.  When
 * clicking on the menu item in the admin, WordPress' menu system thinks you're viewing something under 'Posts'
 * instead of 'Users'.  We really need WP core support for this.
 */
function my_add_profession_admin_page() {

	$tax = get_taxonomy( 'profession' );

	add_users_page(
		esc_attr( $tax->labels->menu_name ),
		esc_attr( $tax->labels->menu_name ),
		$tax->cap->manage_terms,
		'edit-tags.php?taxonomy=' . $tax->name
	);
}

This will at least allow you to create and manage custom terms of the profession taxonomy. If you have a better solution for this, please post it in the comments for other people to see.

Fixing the “Posts” column

After creating the admin page, you probably noticed a column on it called “Posts.” Instead, it should display a “Users” column. The following code will fix this issue and list the number of users with each term from the profession taxonomy.

/* Create custom columns for the manage profession page. */
add_filter( 'manage_edit-profession_columns', 'my_manage_profession_user_column' );

/**
 * Unsets the 'posts' column and adds a 'users' column on the manage profession admin page.
 *
 * @param array $columns An array of columns to be shown in the manage terms table.
 */
function my_manage_profession_user_column( $columns ) {

	unset( $columns['posts'] );

	$columns['users'] = __( 'Users' );

	return $columns;
}

/* Customize the output of the custom column on the manage professions page. */
add_action( 'manage_profession_custom_column', 'my_manage_profession_column', 10, 3 );

/**
 * Displays content for custom columns on the manage professions page in the admin.
 *
 * @param string $display WP just passes an empty string here.
 * @param string $column The name of the custom column.
 * @param int $term_id The ID of the term being displayed in the table.
 */
function my_manage_profession_column( $display, $column, $term_id ) {

	if ( 'users' === $column ) {
		$term = get_term( $term_id, 'profession' );
		echo $term->count;
	}
}

Assigning terms to users

Thus far, you’ve got a custom taxonomy and can add terms for it, but you need a way to assign these terms to individual users. For this example, you’ll be adding a custom section to the edit user/profile page in the admin. It will display a list of radio select boxes so the user can choose their profession.

This is just an extremely basic example. You have tons of room for customization. You can do select elements, checkboxes, a text input, or whatever best works for you.

The following PHP code will add this section to the edit user/profile page.

/* Add section to the edit user page in the admin to select profession. */
add_action( 'show_user_profile', 'my_edit_user_profession_section' );
add_action( 'edit_user_profile', 'my_edit_user_profession_section' );

/**
 * Adds an additional settings section on the edit user/profile page in the admin.  This section allows users to
 * select a profession from a checkbox of terms from the profession taxonomy.  This is just one example of
 * many ways this can be handled.
 *
 * @param object $user The user object currently being edited.
 */
function my_edit_user_profession_section( $user ) {

	$tax = get_taxonomy( 'profession' );

	/* Make sure the user can assign terms of the profession taxonomy before proceeding. */
	if ( !current_user_can( $tax->cap->assign_terms ) )
		return;

	/* Get the terms of the 'profession' taxonomy. */
	$terms = get_terms( 'profession', array( 'hide_empty' => false ) ); ?>

	<h3><?php _e( 'Profession' ); ?></h3>

	<table class="form-table">

		<tr>
			<th><label for="profession"><?php _e( 'Select Profession' ); ?></label></th>

			<td><?php

			/* If there are any profession terms, loop through them and display checkboxes. */
			if ( !empty( $terms ) ) {

				foreach ( $terms as $term ) { ?>
					<input type="radio" name="profession" id="profession-<?php echo esc_attr( $term->slug ); ?>" value="<?php echo esc_attr( $term->slug ); ?>" <?php checked( true, is_object_in_term( $user->ID, 'profession', $term ) ); ?> /> <label for="profession-<?php echo esc_attr( $term->slug ); ?>"><?php echo $term->name; ?></label> <br />
				<?php }
			}

			/* If there are no profession terms, display a message. */
			else {
				_e( 'There are no professions available.' );
			}

			?></td>
		</tr>

	</table>
<?php }

After adding the preceding code, you’ll get a new section near the bottom of your edit user/profile page that looks like the following screenshot.

Assigning taxonomy terms on user profile page

You’ll need a way to save the selected term (profession) for the user. The next code block will handle that.

/* Update the profession terms when the edit user page is updated. */
add_action( 'personal_options_update', 'my_save_user_profession_terms' );
add_action( 'edit_user_profile_update', 'my_save_user_profession_terms' );

/**
 * Saves the term selected on the edit user/profile page in the admin. This function is triggered when the page
 * is updated.  We just grab the posted data and use wp_set_object_terms() to save it.
 *
 * @param int $user_id The ID of the user to save the terms for.
 */
function my_save_user_profession_terms( $user_id ) {

	$tax = get_taxonomy( 'profession' );

	/* Make sure the current user can edit the user and assign terms before proceeding. */
	if ( !current_user_can( 'edit_user', $user_id ) && current_user_can( $tax->cap->assign_terms ) )
		return false;

	$term = esc_attr( $_POST['profession'] );

	/* Sets the terms (we're just using a single term) for the user. */
	wp_set_object_terms( $user_id, array( $term ), 'profession', false);

	clean_object_term_cache( $user_id, 'profession' );
}

Displaying user taxonomy terms

You can use any of the standard WordPress taxonomy functions for listing out your terms. For example, wp_list_categories() and wp_tag_cloud() will both work fine. Each will link to the term archive page (covered in the section below).

If you wanted to show a tag cloud with all of your “professions” (profession cloud) in a theme template, your code would look something like the following.

<?php wp_tag_cloud( array( 'taxonomy' => 'profession' ) ); ?>

One thing to look out for though is that the title attribute for links in the tag cloud will read “1 topic” or “X topics.” An easy way to change this is to write a custom callback function to modify the text. In the following screenshot, you can see this changed from “topics” to “users”.

Tooltip changed to 'users' on term archive link

The following code block is a function that will handle that.

/**
 * Function for outputting the correct text in a tag cloud.  Use as the 'update_topic_count_callback' argument
 * when calling wp_tag_cloud().  Instead of 'topics' it displays 'users'.
 *
 * @param int $count The count of the objects for the term.
 */
function my_profession_count_text( $count ) {
	return sprintf( _n('%s user', '%s users', $count ), number_format_i18n( $count ) );
}

You’d use it when displaying your tag cloud like so:

<?php wp_tag_cloud(
	array(
		'taxonomy' => 'profession',
		'topic_count_text_callback' => 'my_profession_count_text'
	)
); ?>

Templates for term archives

User taxonomy archive page

The template for term archives is handled the same as any other taxonomy. So, if you wanted to create a custom template (you probably should) for your theme to display users by profession, you’ll want to create a template named taxonomy-profession.php.

I won’t cover creating theme templates here. It’s outside the scope of this tutorial.

The big difference between this template and a normal template is there’s no post loop. Instead, you need to create a user loop. The following code should replace your normal post loop in this template.

<?php
$term_id = get_queried_object_id();
$term = get_queried_object();

$users = get_objects_in_term( $term_id, $term->taxonomy );

if ( !empty( $users ) ) {
?>
	<?php foreach ( $users as $user_id ) { ?>

		<div class="user-entry">
			<?php echo get_avatar( get_the_author_meta( 'email', $user_id ), '96' ); ?>
			<h2 class="user-title"><a href="<?php echo esc_url( get_author_posts_url( $user_id ) ); ?>"><?php the_author_meta( 'display_name', $user_id ); ?></a></h2>

			<div class="description">
				<?php echo wpautop( get_the_author_meta( 'description', $user_id ) ); ?>
			</div>
		</div>

	<?php } ?>
<?php } ?>

The most important function used in the code above is the WordPress get_objects_in_term() function. By putting in a term ID and taxonomy name, you can grab an array of all the object (user) IDs with that term (profession). With the user ID, you can load any information about a user you want with any standard WordPress functions.

Of course, you’re free to customize this however you want. The preceding code merely loops through each of the users with a specific profession. It then displays each user’s avatar, name with a link to their posts archive, and description.

Disabling the ‘profession’ username

When you registered the profession taxonomy, you created the slug author/profession so that the profession archive pages would have a URL like yoursite.com/author/profession/designer. The problem with this that “profession” could potentially be a username someone signs up to your site with.

The following code will make sure no one can sign up with this username:

/* Filter the 'sanitize_user' to disable username. */
add_filter( 'sanitize_user', 'my_disable_username' );

/**
 * Disables the 'profession' username when someone registers.  This is to avoid any conflicts with the custom
 * 'author/profession' slug used for the 'rewrite' argument when registering the 'profession' taxonomy.  This
 * will cause WordPress to output an error that the username is invalid if it matches 'profession'.
 *
 * @param string $username The username of the user before registration is complete.
 */
function my_disable_username( $username ) {

	if ( 'profession' === $username )
		$username = '';

	return $username;
}

Of course, if you use a different rewrite structure for your taxonomy, don’t worry about that code.

Time to create your own user taxonomies

Whoah! That was certainly a ton of information to cover in a single tutorial. I’m sure some of you have questions, so please ask away in the comments.

Now it’s time for you to venture out on your own and create some cool stuff. I’d love to hear what ideas you have and see any projects that you use this code in. I’m definitely interested in seeing some practical, real-world use cases of user taxonomies.

One final note: The code in this tutorial is just something I played around with in about a two-hour span. It’s still a work in progress. Honestly, it took me much longer to write this tutorial. Therefore, I leave it up to you, dear reader, to improve upon the code.

http://justintadlock.com/archives/2011/10/20/custom-user-taxonomies-in-wordpress