The default “category” and “tag” taxonomies in WordPress offer a lot of flexibility to those with imagination and in my development experience I have seen a wide range of creative implementations. With the introduction of custom taxonomies and their growing ease of use, though, we need no longer be bound to categories and tags. With the ability to create both hierarchical and non-hierarchical taxonomies and with the introduction of several new features in WordPress 3.1, now is the time, if you’re not already, to begin putting custom taxonomies to use.
In part one of this two part series, we learned how to setup custom post types and custom taxonomies. We also learned how to build a template to check for and display media attached to custom posts. Now, we’ll learn how to use custom taxonomy templates to organize and relate our media. Let’s get started!
Organizing Our Media – Working With Custom Taxonomy Templates
Now that we have our media displaying, it’s time to work on how it’s organized. If you tried clicking on one of the custom taxonomy terms, odds are the result weren’t very exciting. You probably saw something like this:
A rather unhelpful default view of a term in the “presenter” taxonomy.
What we’re going to do next is create a template that allows us to customize the results and offer a page that will be more useful.
Creating A Custom Taxonomy Template
As with custom posts, the WordPress template engine has a custom taxonomy template hierarchy that it follows to determine what template it uses to display data associated with a custom taxonomy term. We’ll start with our “presenters” taxonomy. In our case, the WordPress hierarchy is as follows:
- taxonomy-presenters.php – WordPress will check the theme folder for a file named taxonomy-presenters.php. If it exists, it will use that template to display the content. For different custom taxonomies, simple replace “presenters” with the name of your custom taxonomy.
- taxonomy.php - If no custom taxonomy template is found, WordPress checks for a general taxonomy template.
- archive.php – If no general taxonomy template is used, the WordPress archive template is used.
- index.php – If no archive template is found, WordPress defaults to the old standby – the index.
Note: The WordPress template hierarchy structure also allows templates for specific terms. For instance, in a case where “Jonathan Wold” was a term in the “presenters” taxonomy, I could create a custom template called “taxonomy-presenters-jonathan-wold.php”.
Non-Hierarchical Custom Taxonomy Templates
We’ll start with the non-hierarchical “presenters” custom taxonomy. As with the custom post type examples previously, I will be using minimal examples for each of the templates.
To get started with this example, create a file called taxonomy-presenters.php and upload it to your theme folder. Add the following code:
<?php get_header(); ?> <?php // Get the data we need $presenter = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) ); ?> <div id="container"> <div id="content" class="presenter"> <h1 class="entry-title"><?php echo $presenter->name; ?></h1> <p><?php echo $presenter->description; ?></p> </div> </div> <?php get_footer(); ?>
Previewing a term should now show you a rather empty page with the name of the term and the description (if you entered one when creating or editing the term). In my case, on Twenty Ten, accessing the term “Jonathan Wold” (/presenter/jonathan-wold) looks like this:
A rather basic, yet more useful, view of a custom taxonomy term template.
Before moving on, let’s review the code above to learn what it’s doing and what you can do with it.
$presenter = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) );
This piece of code may seem intimidating at first, but it’s rather simple. First, we are defining a variable called $presenter. Our goal is to have that variable store everything that WordPress knows about our term.
To do that, we are using the function get_term_by. That function requires three things:
- Field – You can access a term by name, ID, or slug. In our case, we are using the slug, which is “jonathan-wold”.
- Value – We’ve told WordPress that we want to get our term data by using the “slug” field. Now, it needs a slug to retrieve data. Since we want this to be dynamic, we are using another function called get_query_var. When you access a term in WordPress (e.g. viewing a term by its permalink), a query is run in order to generate the results for that term. Using “get_query_var” allows you to intercept that query and get the data for your own use.
- Taxonomy – In addition to the term slug, WordPress also needs the taxonomy name (this is critical in cases where the same name is used across multiple taxonomies). We use “get_query_var” again to retrieve that for us.
If we wanted to access the term data for one specific term in a particular custom taxonomy, we would do it like this:
$presenter = get_term_by( 'slug', 'jonathan-wold', 'presenters');
In our example, we are adding code into our template telling WordPress to give us the data for the term a visitor is currently viewing. WordPress stores that data as an “object”.
To see what data is available to you in an object, add the following within your code:
<?php echo '<pre>'; print_r( $presenter ); echo '</pre>'; ?>
Preview the term again and you should see a block of code that looks something like this:
An easily readable view of the object attributes and values.
That block of code lets you see what WordPress knows about your particular object and what information you have available to use within your template.
Note: In part one, I referenced a technique for adding custom fields to your custom taxonomies and giving you access to more data within your templates. Just incase you missed the reference, take a look at the advanced tutorial, How To Add Custom Fields To Custom Taxonomies, on the Sabramedia blog.
Displaying Object Data In Templates
Now, let’s look at how we took the data from that object and actually displayed it in the template. Let’s start with the first example:
<?php echo $presenter->name; ?>
In English, we are telling PHP to “echo”, or display, the “name” value of the $presenter object. We would know that the object created with “get_term_by” contains the value for “name” by either looking up the return values for get_term_by in the Codex or by using “print_r” to see for ourselves. We’ll explore this in more detail once we look at the “topics” taxonomy.
To get our description, we do the same thing, changing the “name” value to “description”:
<?php echo $presenter->description; ?>
Displaying Term Results In Custom Taxonomy Templates
Now that we have our term name and description displaying, it’s time to show some actual custom post results.
We are continuing our example with taxonomy-presenters.php. Replace the existing code with the following:
<?php get_header(); ?> <?php // Get the data we need $presenter = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) ); $resources = new WP_Query( array( 'post_type' => 'resource', // Tell WordPress which post type we want 'posts_per_page' => '3', // Show the first 3 'tax_query' => array( // Return only resources where presenter is listed array( 'taxonomy' => 'presenters', 'field' => 'slug', 'terms' => $presenter->slug, ) ) ) ); ?> <div id="container"> <div id="content" class="presenter"> <h1 class="entry-title"><?php echo $presenter->name; ?></h1> <p><?php echo $presenter->description; ?></p> <div class="resources"> <h3>Latest Resources</h3> <ul id="resource-list"> <?php while ( $resources->have_posts() ) : $resources->the_post(); ?> <li id="resource-<?php the_ID(); ?>" class="resource"> <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a> <span><?php the_excerpt(); ?></span> </li> <?php endwhile; ?> </ul> </div> </div> </div> <?php get_footer(); ?>
Previewing one of your terms should now display the name and description of the term along with a list of custom posts associated with that term. In our case, the results look like this:
With a customizable list of related custom posts, the term results template is looking much more useful.
The update to this code block is the addition of our “$resources” query. Let’s take a look at that more closely:
$resources = new WP_Query( array( 'post_type' => 'resource', // Tell WordPress which post type we want 'posts_per_page' => '3', // Show the first 3 'tax_query' => array( // Return resources associated with presenter array( 'taxonomy' => 'presenters', 'field' => 'slug', 'terms' => $presenter->slug, ) ) ) );
For our variable of $resources, we are creating a new instance of the WordPress class, WP_Query. Then, we’re setting values on several parameters, post_type, post_per_page, and tax_query.
The first two are straight forward. With “post_type”, you let WordPress know which types of content you’re wanting to return. We used that in our media example to retrieve attachments. To display multiple posts types, replace the “post_type” line with this:
'post_type' => array( 'resource', 'other_post_type', 'another_post_type' ),
For “posts_per_page”, you are letting WordPress know how many posts to return before triggering pagination. If you want to return all posts, use “-1″ for the value, like this:
'posts_per_page' => '-1', // Show all the posts
Now, “tax_query” is a new parameter added in WordPress 3.1. It is a powerful parameter that lets you return results associated with multiple taxonomies and custom fields.
Let’s take a closer look at it:
'tax_query' => array( // Return resources associated with presenter array( 'taxonomy' => 'presenters', 'field' => 'slug', 'terms' => $presenter->slug, ) )
First, we choose our custom taxonomy. In our case, we are hardcoding in “presenters”. If we wanted to make it more dynamic and build, for instance, a general taxonomy template (taxonomy.php) to handle multiple taxonomies in a similar way, we would use “get_query_var” again, like so:
'taxonomy' => get_query_var( 'taxonomy' ),
Note: The “tax_query” function works with one taxonomy at a time. To query multiple taxonomies, simply duplicate the code above (be sure to add the appropriate comma at the end) and change the parameters accordingly.
Next, we have the “field” parameter. This lets WordPress know what field we will be returning our terms by. WordPress accepts “slug” or “id”. I am using “slug” because I prefer recognizing posts by words over numbers.
Then, we have “terms”. In our case, we are using the $presenter variable to pass in the “slug” in the same way we added data directly into our custom post template. If we wanted to make it more dynamic, we could use “get_query_var” again:
'term' => get_query_var( 'term' ),
If we want to return results for multiple terms, we add an array, like this:
'term' => array( 'term_1', 'term_2', 'random_other_term' ),
To modify our results further, we can use an optional “operator” parameter that allows us to specify whether our results are “IN”, “NOT IN”, or “OR”. A simple example, appropriate for use in a single taxonomy, is “NOT IN”.
To modify the query to return results that are “NOT IN” the custom taxonomy and terms that you’ve listed, add the following within your tax_query array:
'operator' => 'NOT IN',
Note: To experiment with results queried against multiple custom taxonomies, take a look at “Multiple Taxonomy Handling” under Taxonomy Parameters on the Codex reference for WP_Query.
Now that we’ve gone through that, we reference our newly created query with a loop. Here’s the code again:
<div class="resources"> <h3>Latest Resources</h3> <ul id="resource-list"> <?php while ( $resources->have_posts() ) : $resources->the_post(); ?> <li id="resource-<?php the_ID(); ?>" class="resource"> <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a> <span><?php the_excerpt(); ?></span> </li> <?php endwhile; ?> </ul> </div>
This is another basic instance of The Loop, customized to return results from our $resources query and, in this case, the results returned are “the_ID”, “the_permalink”, “the_title”, and “the_excerpt”.
Checking For Empty Results
In our example above, we have some code (like the <UL>) that appears outside of our loop. If there were no results, the “container” HTML would still show up in the template. To prevent that, we can preface it with a conditional statement like this:
<?php if ( $resources->post_count > 0 ) { // Check to make sure there are resources ?> // Display your results <?php } ?>
Replace “$resources” with the name of your custom query and return your results within the conditional statement. If the “post_count” is greater than zero (“> 0″), then the code will appear in your template – otherwise, the page remains free of extra HTML.
Hierarchical Custom Taxonomy Templates
Alright, now that we have a non-hierarchical taxonomy under our belt, let’s move on and tackle hierarchy. We covered the basics in setting up “presenters”, so let’s pick up there where we left off.
Create a file called taxonomy-topics.php and add the following code:
<?php get_header(); ?> <?php // Get the data we need $topic = get_term_by( 'slug', get_query_var( 'term' ), get_query_var( 'taxonomy' ) ); $resources = new WP_Query( array( 'post_type' => 'resource', // Tell WordPress which post type we want 'posts_per_page' => '3', // Show the first 3 'tax_query' => array( // Return only resources where presenter is listed array( 'taxonomy' => 'topics', 'field' => 'slug', 'terms' => $topic->slug, ) ) ) ); ?> <div id="container"> <div id="content" class="presenter"> <h1 class="entry-title"><?php echo $topic->name; ?></h1> <p><?php echo $topic->description; ?></p> <?php if ( $resources->post_count > 0 ) { // Check to make sure there are resources ?> <div class="resources"> <h3>Latest Resources</h3> <ul id="resource-list"> <?php while ($resources->have_posts()) : $resources->the_post(); ?> <li id="resource-<?php the_ID(); ?>" class="resource"> <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a> <span><?php the_excerpt(); ?></span> </li> <?php endwhile; ?> </ul> </div> <?php } ?> </div> </div> <?php get_footer(); ?>
Previewing a “topic” should now give you a familiar plain template that looks something like this:
A basic, yet useful view of another custom taxonomy term.
Creating Parent and Children Links
Now, the thing that is different with this taxonomy is that it can have both “parents” and “children”. What we want to do is check for a parent topic and, if it exists, display a link to it. We also want to check for sub-topics and if they exist, display links to them.
Note: For these examples to work, be sure that you’re working with a post example that has multiple levels of a hierarchical custom taxonomy associated with it. In my example, I have created topics 3 levels deep and associated all of them with the post.
So let’s get started. First, within the PHP section at the top of our template, add the following code:
if ( $topic->parent > 0 ) { // Check to make sure the topic has a parent $topic_parent = get_term( $topic->parent, 'topics' ); // Get the object for the topic's parent } $topic_children = get_terms( 'topics', 'child_of='.$topic->term_id ); $last_topic = end( array_keys( $topic_children ) ); // Mark the last topic
Alright, what do we have going on here? First, we’re checking to make sure the topic has a parent. If a topic does not have a parent, WordPress gives the “parent” attribute a value of zero (“0″). So, the first thing we do is a conditional to check and make sure that the parent has a value greater than zero. If it does, we define the variable $topic_parent and use the get_term function to retrieve the parent topic based on its ID.
Next, we define another variable called $topic_children. This time, we use the get_terms function, which has a special attribute called “child_of”. We pass in the value of the current topic and tell WordPress, in English, to “take the current topic and bring me back a list of all its sub-topics or children”.
Then, we define a variable called $last_topic. The data that $topic_children gives us is in the form of an array. Our $last_topic variable counts to the “end” of the array and keeps track of it. We’re going to use that later to put a comma after each of our sub-topics and then do nothing for the last sub-topic.
Now, to show the results, add the following code within your template:
<?php if ( $topic->parent > 0 ) { ?> <strong>Parent:</strong> <a href="<?php echo get_term_link( $topic_parent->slug, 'topics' ); ?>"><?php echo $topic_parent->name; ?></a> <?php } ?> <?php if ( $topic_children ) { ?> <strong>Subtopics: </strong> <?php foreach ( $topic_children as $key => $topic_single ) : ?> <span><a href="<?php echo get_term_link( $topic_single->slug, 'topics' ); ?>"><?php echo $topic_single->name; ?></a></span><?php if ( $key !== $last_topic ) echo ', '; ?> <?php endforeach; ?> <?php } ?>
Each block of code first checks to make sure that a parent topic or sub-topic(s) exists, respectively. Then, in the case of the “parent”, we use the get_term_link function to retrieve the link by the “slug” of the $topic_parent.
For our sub-topics, we create a “foreach” loop to output a list of all sub-topics. At the end, we do a conditional check on the $last_topic in our array using the variable we created earlier. If it is not the last topic, we echo a comma after the close <span> – otherwise, we do nothing.
And there you have it! The result using the Twenty Ten theme will look something like this:
A view of the “topics” taxonomy, with the parent topic and sub-topics listed.
Relating Taxonomies By Posts
Now, this is where we get a bit fancy. Let’s say we’re working on our template for the “topics” taxonomy and we wanted to show a list of “presenters” who covered that particular topic. How would we do that? In the code that follows, we’re going to use the custom posts themselves as our reference point and bring back the related custom taxonomies.
The rationale is simple. If we had 10 posts associated with a particular term in a given custom taxonomy, those 10 posts will likely have other terms from other custom taxonomies associated with them as well. So, we use the posts themselves to retrieve and compile the term data that would otherwise not be related to our particular term. Here are some examples where this might be especially useful:
- Events -Â An “event” taxonomy where we want to show a list of “presenters” at that same event.
- Movies -Â A “genre” taxonomy where we want to show a list of “directors” who make that same genre of film.
- Recipes - A “category” taxonomy where we want to show related “ingredients”.
Alright, let’s dive into the code:
// Retrieve all the IDs for resources associated with the current term $post_ids = array(); foreach ( $resources->posts as $post ) { array_push( $post_ids, $post->ID ); } // Get presenter data based on the posts associated with the current term $presenters_by_posts = wp_get_object_terms( $post_ids, "presenters" ); $topic_presenters = array(); foreach ( $presenters_by_posts as $presenter ){ $topic_presenters[$presenter->term_id] = $presenter; } $last_presenter = end( array_keys( $topic_presenters ) );
First, we define an empty array called $post_ids. Then, we create a loop through each of the “resources” associated with our current term using the $resource query we created earlier. We take that loop and “push” each of the post IDs for our resources back into the previously empty $post_ids array.
Next, we define a new variable, $presenters_by_posts. We use the wp_get_object_terms function, which accepts either a single ID or an array of IDs (which we just created) to return a list of terms. In our case, we’re using this function to check all the custom posts associated with this term and bring back a list of all the “presenters”.
Next, we define another empty array called $topic_presenters. We now loop through our $presenters_by_posts and then redefine our $presenter variable to hold the term_id of each $presenter that we returned using our $presenters_by_posts function.
Now, let’s make use of that in the template. Add the following the code:
<?php if ( $topic_presenters ) { ?> <strong>Presenters:</strong> <?php foreach ( $topic_presenters as $key => $presenter ) : ?> <span><a href="<?php echo get_term_link( $presenter->slug, 'presenters' ); ?>"><?php echo $presenter->name; ?></a></span><?php if ( $key !== $last_presenter ) echo ', '; ?> <?php endforeach; ?> <?php } ?>
Now, we simply loop through each of our $topic_presenters using our redefined $presenter. We then access the attribute values of our $presenter object to echo the “slug” for the term link and the term “name”. Finally, we do a check for the $last_presenter and if it is not the last one, we echo a comma.
Here’s how that looks in my example:
The updated view of “Topics”, with a list of presenters related by custom posts.
Conclusion
And that’s a wrap! With part one and part two under your belt you have taken some solid steps above and beyond the basics of WordPress theme development. My goal has been to give you some solid examples that you can follow and to explain what’s been done along the way so you can apply what you’ve learned to your own projects. You’ve learned a lot about custom post types and custom taxonomies and I am looking forward to seeing what you build.
My next area of interest is a tutorial that teaches best practices for customizing the WordPress admin and building new backend interfaces for end-users to manage these increasingly complex media sites. I’m also interested in writing about how to build the front-end filtering interface, demonstrated in the ARISE resource center. If either of those interest you, please let me know.
Credits
As I mentioned in part one, the WordPress Stack Exchange community has been a huge help in sharing techniques and pointing out best practice solutions. If you have a question directly related to this post, ask it here. If you have anything else WordPress related, though, be sure to check out WPSE Also, a big thank you to all my team mates at Sabramedia for their patience with me and all their help testing and providing feedback.
© Jonathan Wold for Smashing Magazine, 2011. |
Permalink |
Post a comment |
Smashing Shop |
Smashing Network |
About Us
Post tags: