Dissecting WordPress Themes Part 9: Taxonomy Hierarchy

A taxonomy in WordPress parlance is a list of terms. Terms from a taxonomy can be associated with posts and custom post types. You are no doubt familiar with two of the taxonomies that ship with WordPress: categories and tags. WordPress also lets you define your own custom taxonomies that are separate from categories and tags. Terms from a custom taxonomy can be attached to posts and/or custom post types as you see fit. As with the other taxonomies, those that you create can also have associated archives so a reader can elect to view all objects associated with a particular term.

In order to demonstrate the archive hierarchy associated with custom taxonomies, we first need to create one. After creating the taxonomy and associated terms, we’ll assign our existing posts to the terms and then demonstrate the template hierarchy. We’ll wrap up by creating a “term cloud” in our footer template.

Create the Custom Taxonomy

Custom taxonomies can be hierarchical like categories or non-hierarchical like tags. For this demonstration we’ll create a non-hierarchical taxonomy called my_terms. This requires that we add some code to our functions.php file as follows:

<?php
add_action('init', 'create_terms_taxonomy');
function create_terms_taxonomy() {
	$labels = array (
	'name'                       => 'Terms',
	'singular_name'              => 'Term',
	'search_items'               => 'Search Terms',
	'popular_items'              => 'Popular Terms',
	'all_items'                  => 'All Terms',
	'parent_item'                => null,
	'parent_item_colon'          => null,
	'edit_item'                  => 'Edit Term',
	'update_item'                => 'Update Term',
	'add_new_item'               => 'Add New Term',
	'new_item_name'              => 'New Term Name',
	'separate_items_with_commas' => 'Separate terms with commas',
	'add_or_remove_items'        => 'Add or remove terms',
	'choose_from_most_used'      => 'Choose from the most used terms',
	'not_found'                  => 'No terms found',
	'menu_name'                  => 'Terms');
	
	$args = array (
	'hierarchical'            => false,
	'labels'                  => $labels,
	'show_ui'                 => true,
	'show_admin_column'       => true,
	'update_count_callback'   => '_update_post_term_count',
	'query_var'               => true,
	'rewrite'                 => array('slug' => 'term'));
	
	register_taxonomy('my_terms', array('post', 'my_book'), $args);
}
?>

We first register a function, create_terms_taxonomy(), on the init action hook. This function sets up some arrays that we pass to the register_taxonomy() function. The first array, $labels, contains various labels for the administration screen interface. The second array, $args, specifies a non-hierarchical taxonomy and contains the $labels array. It also sets the slug for the taxonomy to term. Finally, we call register_taxonomy() with parameters for the taxonomy name (my_terms), an array specifying the objects to which these terms may be associated (post and my_book), and the $args array.

Once the taxonomy has been created, the administration interface provides the Terms menu item under both the Posts and Books menus. If you select either of the Terms menus, you will see the Terms page as shown below. On this page you can create new terms in the taxonomy.

Terms page with sample term

The Terms page associated with our custom taxonomy allows you to create new terms in the taxonomy.

On the Terms page create three new terms as shown in the table below. Be sure to click the Add New Term button for each.

Name Slug Description
Tax 1 tax1 This is the description for Tax 1.
Tax 2 tax2 This is the description for Tax 2.
Tax 3 tax3 This is the description for Tax 3.

Once you have created all of the new terms, your Terms page should like that below.

Completed Terms page

The completed Terms page shows the three terms assigned to our custom taxonomy.

Assign Terms to Posts

We will now assign the new terms to our existing posts. This can be done using the Quick Edit function on the Posts→All Posts page. Assign the terms as shown in the table below. Be sure to click the Update button after assigning terms to each post.

Title Terms
Post 1 Tax 1
Post 2 Tax 2
Post 3 Tax 3
Post 4 Tax 1, Tax 2, Tax 3
Post 5 Tax 2, Tax 3
Post 6 Tax 1, Tax 3
Post 7 Tax 1, Tax 2
Post 8 Tax 3
Post 9 Tax 2
Post 10 Tax 1
Book 1 Tax 3
Book 2 Tax 2
Book 3 Tax 1

After making all of the assignments, the Posts page should look like the snapshot below.

Posts page showing terms assignments

The Posts page shows the assignments of Terms to our existing posts and custom post type.

Display Terms Assignments

Next, we’ll add the new taxonomy assignments to our index.php template. This just takes a few lines of code to add a new column to the table and get the terms with the get_the_term_list() call as shown below.

<?php
get_header(); 
echo "<p>In index.php</p>\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</th><th>Author</th>",
	     "<th>Pub Date</th><th>Category</th><th>Tags</th><th>Terms</th>",
	     "</tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
				get_the_title(), "</a></td>";
		$postFormat = get_post_format();
		echo "<td>", $postFormat ? $postFormat : 'standard', "</td>";
		echo "<td>", the_author_posts_link(), "</td>";
		$year = get_the_time('Y');
		$month = get_the_time('m');
		$day = get_the_time('d');
		echo "<td><a href='", get_day_link($year, $month, $day), "'>",
			get_the_date(), "</a></td>";
		echo "<td>", get_the_category_list(', '), "</td>";
		echo "<td>", get_the_tag_list('', ', '), "</td>";
		echo "<td>", get_the_term_list('', 'my_terms', '', ', '), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}
    
get_sidebar();
get_footer();
?>

Now you can visit the site by clicking the Visit Site link in the toolbar. You should see the first page of posts and their taxonomy assignments as shown below. Click the Older posts link to also view the first three posts and check their assignments.

Index.php template with term assignments

After adding the Terms column to our index.php template, we can view the term assignments and click their associated links. Each link navigates to the archive page for its term.

Hover your mouse over the links in the Terms column of the table. Notice that the URLs are in the format http://localhost/lab1/term/[slug]/ where [slug] is the slug assigned to a term. The term portion of the URL is from the rewrite parameter passed to register_taxonomy() in the $args array. Click one of the term links, say Tax 2, to view the taxonomy archive for the selected term. You should see the page below.

Note: After creating a new rewrite slug, as you did with term in the register_taxonomy() call earlier, you may need to re-generate your rewrite rules in order for it to take effect. You can do this easily by navigating to the Permalinks page (Settings→Permalinks from the administration screen menu) and clicking the Save Changes button.
Archive.php template showing selected term archive

The archive.php template overrides index.php and shows the posts assigned to the selected term.

Notice that the archive.php template is executed to fulfill the request for this taxonomy archive. The archive.php template is overriding index.php as it does for all archive pages. To verify this, you can temporarily rename archive.php and refresh the page. This will cause WordPress to revert to index.php. Restore the archive.php filename before proceeding.

Next, let’s add the Terms taxonomy column to the post table in archive.php as we did for index.php. The revised code for archive.php is below.

<?php
get_header(); 
echo "<p>In archive.php</p>\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</th><th>Author</th>",
	     "<th>Pub Date</th><th>Category</th><th>Tags</th><th>Terms</th>",
	     "</tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
				get_the_title(), "</a></td>";
		$postFormat = get_post_format();
		echo "<td>", $postFormat ? $postFormat : 'standard', "</td>";
		echo "<td>", the_author_posts_link(), "</td>";
		$year = get_the_time('Y');
		$month = get_the_time('m');
		$day = get_the_time('d');
		echo "<td><a href='", get_day_link($year, $month, $day), "'>",
			get_the_date(), "</a></td>";
		echo "<td>", get_the_category_list(', '), "</td>";
		echo "<td>", get_the_tag_list('', ', '), "</td>";
		echo "<td>", get_the_term_list('', 'my_terms', '', ', '), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}
    
get_sidebar();
get_footer();
?>

Now refresh the archive page and observe that all of the posts have indeed been assigned the Tax 2 term. You might want to check the other terms in the taxonomy as well.

Archive.php with assigned taxonomy terms

The archive.php template now shows the assigned terms from the custom taxonomy.

Exercise the Hierarchy

The first stop in the custom taxonomy hierarchy is taxonomy.php. This template provides a mechanism for achieving special formatting of custom taxonomy archives separately from the other archive types. Copy the archive.php template file to taxonomy.php. Change the trace message at the beginning of the file to In taxonomy.php. Also add a message indicating the name of the term you’re browsing along with its description. Finally, since we now have a message indicating the taxonomy term we’re browsing, we’ll remove the redundant Terms column. Here’s the code for taxonomy.php.

<?php
get_header(); 
echo "<p>In taxonomy.php</p>\n";
echo "<p>Browsing term ", single_term_title(), "</p>\n";
echo term_description();

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</th><th>Author</th>",
	     "<th>Pub Date</th><th>Category</th><th>Tags</th></tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
				get_the_title(), "</a></td>";
		$postFormat = get_post_format();
		echo "<td>", $postFormat ? $postFormat : 'standard', "</td>";
		echo "<td>", the_author_posts_link(), "</td>";
		$year = get_the_time('Y');
		$month = get_the_time('m');
		$day = get_the_time('d');
		echo "<td><a href='", get_day_link($year, $month, $day), "'>",
			get_the_date(), "</a></td>";
		echo "<td>", get_the_category_list(', '), "</td>";
		echo "<td>", get_the_tag_list('', ', '), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}
    
get_sidebar();
get_footer();
?>

Note that this code is very similar to category.php and tag.php. It retrieves the current taxonomy term with a call to single_term_title() and the term’s description with term_description(). If you refresh the page for one of your taxonomy archives, say Tax 2, you will see the taxonomy.php template in action:

Taxonomy.php overrides archive.php for custom taxonomies

The taxonomy.php template can be deployed to override archive.php for special formatting of custom taxonomy archives.

Since categories and tags are also implemented as taxonomies, you may well wonder if taxonomy.php might override category.php and tag.php or their descendent templates. Well, it’s easy enough to find out. Return to the home page of your site and click the various category and tag links. Notice that category.php, tag.php, and the individual category/tag templates we created back in Part 5 and Part 6 of the series, are still executed as before. Even if you temporarily rename category.php, for example, the fallback template is archive.php and not taxonomy.php. This illustrates that taxonomy.php only applies to custom taxonomies and not the built-in taxonomies.

There are two additional levels to the taxonomy archive hierarchy: one that applies to the taxonomy as a whole and the other to the individual terms of the taxonomy. The format of the taxonomy-specific template is taxonomy-[taxonomy].php where [taxonomy] is the name of the taxonomy as provided in the call to register_taxonomy(). In our case, the name is my_terms.

In order to demonstrate this template, copy taxonomy.php to taxonomy-my_terms.php. Change the trace message to In taxonomy-my_terms.php. The completed code follows:

<?php
get_header(); 
echo "<p>In taxonomy-my_terms.php</p>\n";
echo "<p>Browsing term ", single_term_title(), "</p>\n";
echo term_description();

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</th><th>Author</th>",
	     "<th>Pub Date</th><th>Category</th><th>Tags</th></tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
				get_the_title(), "</a></td>";
		$postFormat = get_post_format();
		echo "<td>", $postFormat ? $postFormat : 'standard', "</td>";
		echo "<td>", the_author_posts_link(), "</td>";
		$year = get_the_time('Y');
		$month = get_the_time('m');
		$day = get_the_time('d');
		echo "<td><a href='", get_day_link($year, $month, $day), "'>",
			get_the_date(), "</a></td>";
		echo "<td>", get_the_category_list(', '), "</td>";
		echo "<td>", get_the_tag_list('', ', '), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}
    
get_sidebar();
get_footer();
?>

If you now select one of the term links, say Tax 1, you will see that taxonomy-my_terms.php overrides taxonomy.php as shown below. Check the other terms to ensure that they all use taxonomy-my_terms.php. Of course, with only a single custom taxonomy there isn’t much need to provide a special template for it, but if you had several custom taxonomies you could provide specific formatting for each using this template pattern.

Taxonomy-my_terms.php overrided taxonomy.php

The template taxonomy-[taxonomy].php provides special formatting for the custom taxonomy named in the template. In this case our custom taxonomy called my_terms is targeted with the taxonomy-my_terms.php template file.

The next level in the taxonomy archive hierarchy is for an individual term. The format of this template is taxonomy-[taxonomy]-[term].php where [taxonomy] is the name of the taxonomy and [term] is the slug for the specific term you want to target. To demonstrate, copy the taxonomy-my_terms.php template to taxonomy-my_terms-tax1.php (pay close attention to the punctuation in the template filename). Change the trace message to In taxonomy-my_terms-tax1.php.

<?php
get_header(); 
echo "<p>In taxonomy-my_terms-tax1.php</p>\n";
echo "<p>Browsing term ", single_term_title(), "</p>\n";
echo term_description();

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</th><th>Author</th>",
	     "<th>Pub Date</th><th>Category</th><th>Tags</th></tr>\n";
	while (have_posts()) {   // start the loop
		the_post();
		echo "<tr>";
		echo "<td>", get_the_ID(), "</td>";
		echo "<td><a href='", get_permalink(), "'>",
				get_the_title(), "</a></td>";
		$postFormat = get_post_format();
		echo "<td>", $postFormat ? $postFormat : 'standard', "</td>";
		echo "<td>", the_author_posts_link(), "</td>";
		$year = get_the_time('Y');
		$month = get_the_time('m');
		$day = get_the_time('d');
		echo "<td><a href='", get_day_link($year, $month, $day), "'>",
			get_the_date(), "</a></td>";
		echo "<td>", get_the_category_list(', '), "</td>";
		echo "<td>", get_the_tag_list('', ', '), "</td>";
		echo "</tr>\n";
		}
	echo "</table>\n";
	next_posts_link("Older posts");
	echo "<br/>";
	previous_posts_link("Newer posts");
} else {
	echo "<p>no posts to display</p>\n";
}
    
get_sidebar();
get_footer();
?>

Now select one of the links for the Tax 1 term. You should see the page shown below. Note that the new template file, taxonomy-my_terms-tax1.php, overrides taxonomy-my_terms.php as expected. Navigate back to the home page and select the other two terms, Tax 2 and Tax 3. Observe that they still use the taxonomy-my_terms.php template as before.

The taxonomy-my_terms-tax1.php template targets the Tax 1 term

The final level of the custom taxonomy archive allows you to target individual terms for special formatting. Here, we target the Tax 1 term with the template file taxonomy-my_terms-tax1.php.

That’s it for the custom taxonomy archive. The template hierarchy diagram is updated below.

Template Hierarchy with Custom Taxonomy Archives

Newly added in this article is the custom taxonomy hierarchy. As with the other archives, archive.php will override index.php. However, special-purpose formatting for custom taxonomies can be provided with taxonomy.php. Additionally, individual taxonomies can be formatted with taxonomy-[taxonomy].php and individual terms with taxonomy-[taxonomy]-[term].php where [taxonomy] is the name of the custom taxonomy and [term] is the slug for the targeted term within the taxonomy.

Display Taxonomy Cloud in the Footer

Since we designed our custom taxonomy to resemble the tag taxonomy, let’s add a “term cloud” for it in the footer. This is very simple to do since it uses the same call that the tag cloud uses, wp_tag_cloud(). Recall that we added a tag cloud back in Part 6 by calling wp_tag_cloud() with no arguments. In order to create a term cloud for our custom taxonomy, we just need to add the name of the taxonomy as an argument. The updated footer.php is shown below.

<?php
echo "<p>In footer.php</p>\n";
echo "Tags: "; wp_tag_cloud();;
echo "<br />";
echo "Terms: "; wp_tag_cloud(array('taxonomy'=>'my_terms'));

echo "<p>Authors:</p><ul>", wp_list_authors(), "</ul>";

echo "<p>Yearly Archives:</p>\n";
echo "<ul>\n";
wp_get_archives(array('type'=>'yearly'));
echo "</ul>\n";

echo "</body>\n";
echo "</html>\n";
?>

Navigate to the site’s home page and notice that we now have two tag clouds in the footer: one for the standard tags taxonomy and another for our custom taxonomy, my_terms, as shown below. The URLs generated by wp_tag_cloud() are the same as those on our index.php and archive.php pages that navigate to the archive page for each term.

Tag cloud for custom taxonomy

We can add a tag cloud for terms in our custom taxonomy.

As another option, you can combine the tag and term clouds into a single list. You do this by passing an array of taxonomies to wp_tag_cloud() as shown below.

<?php
echo "<p>In footer.php</p>\n";
echo "Tags: "; wp_tag_cloud();
echo "<br />";
echo "Terms: "; wp_tag_cloud(array('taxonomy'=>'my_terms'));
echo "<br />";
echo "Combined: "; wp_tag_cloud(array('taxonomy'=>
                             array('post_tag','my_terms')));

echo "<p>Authors:</p><ul>", wp_list_authors(), "</ul>";

echo "<p>Yearly Archives:</p>\n";
echo "<ul>\n";
wp_get_archives(array('type'=>'yearly'));
echo "</ul>\n";

echo "</body>\n";
echo "</html>\n";
?>

This code will result in a combined cloud of tags and terms.

Combined tag cloud

You can also combine tags from the built-in tags taxonomy with the custom terms taxonomy to form a consolidate tag cloud.

In this article we continued our exploration of the archive hierarchies by examining the custom taxonomy archive. We created a non-hierarchical custom taxonomy, added terms to it, and assigned those terms to our existing posts and custom post type. We then implemented the taxonomy.php, taxonomy-[taxonomy].php, and taxonomy-[taxonomy]-[term].php templates to demonstrate how each more specific template overrides its more general parent in the template hierarchy. We finished up with a brief look at the wp_tag_cloud() call to create a term cloud in our footer.php template.

In the next article in the series, we’ll wrap up our archive hierarchy demonstrations with the custom post type archive.

References

Speak Your Mind

*