Categories are better than tags! Why? Because a category can have child categories, and grand-child categories and so on. You can do all sorts of interesting things with them.

Say you want to display only the categories related to a certain post, while maintaining the hierarchy.

You could try to painstakingly re-create the category tree from the bottom up and then display it somehow.

Or… you could choose to simply skip the categories you don’t want to have displayed. Makes sense? Read on.

Enter Walkers

A Walker is a special class for displaying various tree-like structures. WordPress uses them for displaying categories, pages and threaded comments.

The wp_list_categories() template tag uses one of them internally. It’s called Walker_Category. The nice bit is that you can replace the default walker with your own.

Walk Your Own Way

First we’ll need to make our own walker, using Walker_Category as a base:

class Post_Category_Walker extends Walker_Category {
 
	private $term_ids = array();
 
	function __construct( $post_id, $taxonomy )  {
		// fetch the list of term ids for the given post
		$this->term_ids = wp_get_post_terms( $post_id, $taxonomy, 'fields=ids' );
	}
 
	function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
		$display = false;
 
		$id = $element->term_id;
 
		if ( in_array( $id, $this->term_ids ) ) {
			// the current term is in the list
			$display = true;
		}
		elseif ( isset( $children_elements[ $id ] ) ) {
			// the current term has children
			foreach ( $children_elements[ $id ] as $child ) {
				if ( in_array( $child->term_id, $this->term_ids ) ) {
					// one of the term's children is in the list
					$display = true;
					// can stop searching now
					break;
				}
			}
		}
 
		if ( $display )
			parent::display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output );
	}
}

display_element() is a method in the Walker class. It’s called once for each element.

What happens here is that we first check if either the current element or one of its children is in the category list of the given post. If it is, we let Walker_Category do the actual displaying.

All that’s left to do now is pass our enhanced walker to wp_list_categories(). We’ll wrap it up in a function, for convenience:

function walk_post_categories( $post_id, $args = array() ) {
	$args = wp_parse_args( $args, array(
		'taxonomy' => 'category'
	) );
 
	$args['walker'] = new Post_Category_Walker( $post_id, $args['taxonomy'] );
 
	$output = wp_list_categories( $args );
	if ( $output )
		return $output;
}

And that’s about it. You now have a neat little template tag:

<?php walk_post_categories(123); ?>

You can even pass additional arguments to wp_list_categories:

<?php walk_post_categories(123, 'show_count=1&title_li='); ?>

Keep On Walking

Of course, this is just one example. You can use this approach with any type of walker. Let me know what other uses you’ve found.

Reactions (2)

Comments (25)

  • Angelia says:

    Okay Scribu, you are officially my hero. I have been beating my head against a wall for so long now with this.

    I actually got so excited to have this knowledge that for a brief insane moment I tried to resurrect my meager attempt at creating a widget to do this ( dissected out of your drill down widget ) so that I wouldn’t have to make the multiple functions for each taxonomy, and could instead just pull down to select from a list of taxonomies, but, alas, it didn’t take me long to realize that even that is still out of my league for now. heh. I have such high hopes for myself ;-)

    I made the functions, and implemented, and I’m over the moon with joy and relief now, so, again, thanks.

    The only glitch I’m having is that the wp_list_categories args don’t seem to be getting passed on. The categories header still shows up etc..

    I implemented like this–
    walk_post_tax_material( $post->ID, ‘show_count=1′, ‘title_li=’ );

    Is it me?

    I’m going to go post followups everywhere that I’ve been begging for help on this, letting others know to come straight here. I have a feeling that once 3.0 is official, there will be many folks struggling to find this functionality as well.

    • scribu says:

      You don’t need a separate function for each taxonomy. Just pass multiple args:

      walk_post_categories( $post->ID, 'taxonomy=material&show_count=1&title_li=');

      Or, if you like arrays:

      walk_post_categories( $post->ID, array(
        'taxonomy' => 'material',
        'show_count' => 1,
        'title_li' => false
      ) );
      
  • Angelia says:

    Okay, this is really showing my ignorance of the most basic concepts in php programming, but, previously, I put the walker class and the walk_post_categories function in my functions.php file.

    Then in my template, I called the function with the arguments. This didn’t work, which seemed to make sense to me since the argument for taxonomy – category was already being parsed in the functions file. When I changed the argument to materials in the functions file, and set post->ID in the call from the template, all worked.

    This is when I figured that it was necessary to make a separate function in the functions file for each taxonomy, and call each from the template. This worked with the exception of ignoring the other arguments like showcount.

    I’ve just tried the above, and in my functions.php file I have the walker category code, then below that:

     
    function walk_post_categories( $post_id, $args = array() ) {
    	$args = wp_parse_args( array(
    		'taxonomy' =&gt; 'category'
    	) );
     
    	$args['walker'] = new Post_Category_Walker( $post_id, $args['taxonomy'] );
     
    	wp_list_categories( $args );
    }

    and then in my template:

     
    walk_post_categories( $post-&gt;ID, 'taxonomy=material&amp;show_count=1&amp;title_li=');

    And darned if the only results I am getting are the word ‘category’ returned in my template. But, again, if I change ‘taxonomy’=> ‘category’ to ‘material’ I get the results I’m looking for.

    This leads me to believe that I am just not properly understanding where the passing of walker category to wp_list_categories is supposed to take place. I know that I need that function in the function.php file in order to pass the arguments and create a new walker each time, but, I can’t understand how that hard coded ‘taxonomy’ => ‘category’ part in the middle of that function works. It seems to just be contradicting the arguments that I am trying to pass from the template.

    I’m so sorry for torturing you through having to explain this any further.

    • scribu says:

      Ah, there was a bug in my function. I’ve updated the tutorial.

      In walk_post_categories() I changed:

      $args = wp_parse_args( array(

      to

      $args = wp_parse_args( $args, array(

      Thanks for catching that.

  • Angelia says:

    Oh yay!!! It works perfectly and this has just made my day!! Thank you again so much Scribu!!

  • Nice write up Scribu! This is one of those topics that is really documented anywhere so it’s good to see it covered here. I recently responded to a WordPress support request with a solution to display a list of pages separated by commas. The solution involved a custom walker class.

  • Thanks for this. You just saved me hours of work.

    One change: There’s an “echo” option you can pass to the walker (default ’1′) that says to show output. If you pass “echo=0″ the class tries to return the output, but your wrapper function doesn’t capture it and pass it back.

    You can make this change for the last line of the wrapper:

    $output = wp_list_categories( $args );
    if( $output ){
    return $output;
    }

  • I am new to WordPress, and this “walker” seems to be just what I am looking to do. In short I would like to show the current page’s associated taxonomy terms (preferably hierarchically) in a widget.

    For example, if the current post has a Taxonomy term “Tom Hanks” in the Taxonomy of “Actor”, the widget would list “Actor” then “Tom Hanks”. Even though there are many other actors in the taxonomy of “Actors”, only the ones from the current page, are listed.

    What you describe seems right on – did you ever turn this into a widget? If not, how would I do that?

  • Hi there, first of all I’d like to thank you, as Angelia and others already said you made my day!

    I’d just like to know if there’s a way to remove the links to the taxonomy terms page from my tree, so it seams just like a list.

    Thanks in advance!
    Good job!

    Bye
    Carletto

  • Nathan says:

    It is possible to append actual posts to this? I need to show a custom taxonomy set of categories as an indented list with the actual posts appearing after the last subcategory. Having some problems getting both the posts and the nicely structured ULs at the same time… Doing lots of searching but haven’t hit on the solution yet…

  • how can we add an extra css class style to parent categories?

  • Alex Hannah says:

    I am working with our web development team and we have hit a stumbling block. What we have is several category templates on our site. We also have categories who have sub-categories then sub-sub categories. What we are trying to do is on our sub-categories page, we would like to display the category name, then if possible display the top 5 most recent posts from that category, all inside a loop. The logic would look like this:

    Category 1:
    Post 1
    Post 2
    Post 3

    Category 2:
    Post 1
    Post 2
    Post 3

    I have done a TON of research before posting here, it looks like everyone is saying that creating a custom WALKER class is the only way to accomplish this. I have tried to impliment the following code:

    [please follow http://codex.wordpress.org/Forum_Welcome#Posting_Code for posting code]

    //USAGE:
    //wp_list_categories( array( ‘title_li’ => ”, ‘echo’ => false , ‘walker’ => new PCWalker() ) ) .

    class PCWalker extends Walker_Category {

    function end_el(&$output, $page, $depth, $args) {
    global $wpdb;
    $output .= “called with: ” . $page->term_id;
    $posts = $wpdb->get_results(“select object_id as ID from wp_term_relationships r ”
    . “join wp_posts p on r.object_id = p.ID where p.post_status = ‘publish’ and r.term_taxonomy_id = ”
    . “(SELECT term_taxonomy_id FROM wp_term_taxonomy WHERE taxonomy = ‘category’ and term_id = ” . $page->term_id . “)”);
    if($posts) :
    $output .= ”; foreach($posts as $post) { $output .= ‘

    ‘; $output .= ‘ID) . ‘” href=”‘ . get_permalink($post->ID) . ‘”>’ . get_the_title($post->ID) . ‘‘; $output .= ‘
    ‘; } $output .= ‘
    ‘;
    endif;
    parent::end_el(&$output, $page, $depth, $args);
    }
    }

    There is a great article explaining the problem and how to fix it, I am just not sure how to impliment this on the category page:
    http://cselian.com/blog/tech/apps/wp-cats-n-posts-in-a-tree/

    I have added the Class to my functions.php file inside my theme, created the function, and I have the following query working inside MySQL, just not sure what is wrong:

    select object_id, p.post_status from uccx_term_relationships r
    join uccx_posts p on r.object_id = p.ID
    where p.post_status = ‘publish’ and r.term_taxonomy_id =
    (SELECT term_taxonomy_id FROM uccx_term_taxonomy WHERE taxonomy = ‘category’ and term_id = 7)

    Any help would be greatly appreciated. We are on a tight deadline, and I have invested $$$ in this website.

    Thanks!

    Alex

  • Henry says:

    WordPress should publish this tutorial on their homepage. Seriously, i’ve been trying to list taxonomy terms with nested subterms associated with a post for the hole day. Pretty much every tutorial i’ve come across took me down the wrong path.

    Thankfully extending the category walker saved my day.

  • Henry says:

    Currently the terms are outputted as links. May I ask if the terms can be outputted as plain text?

  • Henry says:

    Hi guys,

    I am still interested in being able to output the terms as plain text (not hyperlinked). is this possible?

    Thanks

Respond / add a comment


Subscribe without commenting