Dissecting WordPress Themes Part 5: Category Hierarchy

WordPress allows readers to search for article archives by several methods: categories, tags, date, author, and custom taxonomy. In this article, we’ll begin our study of the archive hierarchy with categories. The category is often used as the primary method of filing a post, allowing users to perform an archive search using the broadest subject-area criteria. Although a post can be filed under more than one category at a time, it is usually recommended to use just a few categories (or perhaps just one) for a post. You can then use many tags to describe what a post is about, much like keywords.

In the prior two articles in the series, Part 3 and Part 4, we looked at the single-post hierarchy. Rooted in this hierarchy is the single.php template file, which serves as the fallback for single-post.php and single-attachment.php (both special cases of the pattern single-[post type].php. This hierarchy is designed to display a single post or page at a time. The archive hierarchy, by contrast, is designed to display many posts at once.

Create and Assign Categories

Before I demonstrate the category hierarchy, we’ll need to create some categories and assign them to our posts. Recall from the last article that we currently have 10 posts and three “Books,” a custom post type that we created. You can create categories at the time you create or edit a post or through the Posts→Categories page in the Administration Screen. We’ll use the latter method so that we can also assign descriptions to our categories as we create them.

Log in to the Administration Screen and navigate to Posts→Categories. Add a new category by typing the name Cat A into the Name field, cat-a into the Slug field, and This is the description of Cat A into the description field as shown below. Click Add New Category.

Categories page

Add new categories on the Categories page. Uncategorized is the default category and cannot be deleted.

Repeat this step twice more to create two additional categories, Cat B and Cat C. All of the categories are shown in the table below.

Category Name Slug Description
Cat A cat-a This is the description of Cat A.
Cat B cat-b This is the description of Cat B.
Cat C cat-c This is the description of Cat C.

We next need to assign these categories to our posts. Navigate to the Posts→All Posts page and edit the posts, assigning the categories as shown in the table below. You can use the Quick Edit link under each post title to update the category check boxes. Be sure to un-check the Uncategorized category (for Posts 1-6) and click Update after editing each post.

Post Title Category
Post 1 Cat A
Post 2 Cat B
Post 3 Cat C
Post 4 Cat A, Cat B
Post 5 Cat B, Cat C
Post 6 Cat A, Cat B, Cat C
Post 7 Uncategorized
Post 8 Uncategorized
Post 9 Uncategorized
Post 10 Uncategorized

When completed, the Posts→All Posts page should look like this:

All Posts page showing assigned categories

The All Posts page shows the category assigned to each post.

Next, navigate to Books→Books. Edit the three books to assign the categories shown below.

Book Title Category
Book 1 Cat A
Book 2 Cat B
Book 3 Cat C

When completed, the Books→Book page should look like this:

Books page showing category assignments

The Books page shows category assignments to our Books custom post type.

Display Assigned Categories

Now that we’ve created our categories and assigned them to our posts, let’s take a look at the site. Navigate to the site’s home page by hovering your mouse cursor over the site name in the Administration Screen toolbar and clicking the pop-up Visit Site link. Since we are not requesting any particular post on the home page, you should see the familiar list of posts generated by our index.php template file.

Home page from last article

The home page from the last article.

Let’s add a column to the post table (after the Format column) with links to each of the assigned categories. We simply add a new column header called Category and a new column in the Loop with a call to get_the_category_list(). Alter the index.php template file 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>Category</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>", get_the_category_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();
?>

After saving your changes, refresh the home page in the browser. It should have an additional column with hyper-linked category names as we assigned in the last section.

Revised index.php file with Categories column

Revised index.php template file with added Category column showing the categories assigned to each post and Book. Since there are more than 10 items, the oldest three are on the Older posts page.

Click the Older posts link to verify that the first three posts are also assigned categories as shown below.

Revised index.php with category column

The oldest three posts are also assigned categories.

Hover your mouse cursor over several of the category links and notice the URLs. The get_the_category_list() function call returned URLs of the form http://localhost/lab1/category/[slug]/. The category component of the URL indicates that we are requesting a category archive page and the slug portion identifies the requested category. Now, click on the link labeled Cat A. The index.php template file executes and the Loop automatically filters the results to just those posts that are assigned to the Cat A category.

index.php showing archive page

Selecting a category (Cat A in this case) causes the Loop in index.php to automatically search for and filter the posts belonging to the selected category.

But what happened to Book 1? It was also assigned to Cat A but is not showing up in our Cat A query. The problem lies in the function we registered with the pre-get-posts action hook  in our functions.php template file (see Part 3). That code currently checks if we are on the home page and, if so, merges posts in the my_book custom post type with our other posts.

<?php
add_action('after_setup_theme', 'myThemeSetup');
function myThemeSetup() {
	// add post formats support
	add_theme_support('post-formats',
		array('aside', 'audio', 'chat', 'gallery',
			'image', 'link', 'quote', 'status', 'video'));
}

add_action('init', 'myPostTypes');
function myPostTypes() {
	register_post_type ('my_book',
		array ('labels' => array (
				'name' => 'Books',
				'singular_name' => 'Book'),
			'public' => true,
			'has_archive' => true,
			'rewrite' => array ('slug' => 'books'),
			'supports' => array ('title', 'editor', 'author', 'excerpt',
				'revisions', 'comments', 'post-formats'),
			'taxonomies' => array ('category', 'post_tag')
			));
}

add_action('pre_get_posts', 'addMyPostTypesToQuery');
function addMyPostTypesToQuery($query) {
	if (is_home() && $query->is_main_query()) {
		$query->set('post_type', array('post', 'my_book'));
	}
}
?>

However, for our archive page demonstration, we would also like the posts merged on category archive pages as well as on the home page. All we need to do is to add another condition to the if-else block as shown below:

<?php
add_action('after_setup_theme', 'myThemeSetup');
function myThemeSetup() {
	// add post formats support
	add_theme_support('post-formats',
		array('aside', 'audio', 'chat', 'gallery',
			'image', 'link', 'quote', 'status', 'video'));
}

add_action('init', 'myPostTypes');
function myPostTypes() {
	register_post_type ('my_book',
		array ('labels' => array (
				'name' => 'Books',
				'singular_name' => 'Book'),
			'public' => true,
			'has_archive' => true,
			'rewrite' => array ('slug' => 'books'),
			'supports' => array ('title', 'editor', 'author', 'excerpt',
				'revisions', 'comments', 'post-formats'),
			'taxonomies' => array ('category', 'post_tag')
			));
}

add_action('pre_get_posts', 'addMyPostTypesToQuery');
function addMyPostTypesToQuery($query) {
	if ( (is_home() || is_category()) && $query->is_main_query() ) {
		$query->set('post_type', array('post', 'my_book'));
	}
}
?>

We added a condition to check if we are on the home page or (||) a category archive page with the is_category() conditional tag. Now, the normal posts as well as posts in the my_book custom post type will be merged on the home page and on archive pages. Refresh the browser for the Cat A category page to see the results.

Category page with normal posts and my_book posts

A small change to our functions.php file merges normal posts and my_book posts on both the home page and on category pages.

Let’s check a few of the other categories. Click on the Cat B link and note that the Loop automatically filters all of the posts that have been assigned to Cat B. Finally, navigate back to the home page and select the Uncategorized link. You should see a list of the uncategorized posts as shown below.

Archive category page for uncategorized posts

The Uncategorized link displays the category archive page for those posts that have not been assigned to a specific category.

The Uncategorized category is just implemented as a special category with that name. The main difference between it and the other categories is that it can’t be deleted or renamed.

Exercise the Hierarchy

We’ve seen how to create categories and assign them to posts. We’ve also seen how the Loop behaves on category archive pages by filtering the post list for the category selected. Let’s now take a look at the template hierarchy for archive pages. As we have seen already, absent any archive template files, index.php is used as the backup. However, we can achieve special formatting for archives by creating a new template file called archive.php.

To demonstrate, copy the index.php file to a new file called archive.php. Change the trace message at the top to In archive.php.

<?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>Category</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>", get_the_category_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();
?>

Refresh the browser for one of the category archives, say Cat A. You should now see that the archive.php file executes instead of index.php.

Template file archive.php overrides index.php

The archive.php template file, when present, overrides index.php for archive requests. This template file is used for all types of archives: category, tag, author, date, and custom taxonomy.

This template file will be used for all archives (including categories, tags, authors, dates, and custom taxonomies) unless there is a more specific template available. If you need special formatting for category archives, you can override archive.php with category.php.

Let’s copy archive.php to category.php and change the trace message to In category.php. We’ll also add trace messages indicating the category name selected and the category’s description. Finally, let’s remove the Category column from the table since we know what category we’re looking at from the messages. Here’s the category.php code.

<?php
get_header(); 
echo "<p>In category.php</p>\n";
echo "<p>Browsing category ", single_cat_title(), "</p>\n";
echo category_description(), "\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</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 "</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();
?>

To get the category name we use single_cat_title() and to get the category description we use category_description(). Refresh the browser and you should see output from category.php as shown below.

Template file category.php overrides archive.php

Since there is now a category.php template file in the theme directory, it overrides archive.php for category archives.

The next level in the category hierarchy uses the category’s ID. This template file would allow you to obtain specialized formatting for a particular category. Let’s take a look by creating a new template file corresponding to one of our categories, say Cat A. In order to determine the ID for a category, navigate to the Edit Category page using the Administration Screen’s Posts→Categories menu item and then selecting the Edit link under the Cat A category. You should see a page similar to that shown below.

Category ID shown in URL

You can find the ID for a category by examining the URL on the Edit Category page. The ID is embedded in the query string of the URL as the value of the tag_ID parameter.

Look in the URL for the query string parameter named tag_ID. Its value is 11 on my site as shown in the screenshot above. The ID will likely be different on your site. Note the ID for the category for use in the next step.

Copy the category.php file to category-[ID].php, where [ID] is the ID value for Cat A on your system. Change the trace message in the new template file to In category-[ID].php, replacing [ID] with your ID. In my case, I copied category.php to category-11.php as shown below.

<?php
get_header(); 
echo "<p>In category-11.php</p>\n";
echo "<p>Browsing category ", single_cat_title(), "</p>\n";
echo category_description(), "\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</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 "</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();
?>

Visit the site and select the Cat A link from one of the posts. You should see the customized category-[ID].php file as shown below. Check the Cat B and Cat C archives to verify that they still use category.php as before.

Template file category-11.php

For specific formatting of a particular category, you can use the category-[ID].php template file. Here, category-11.php overrides category.php for Cat A.

The final layer in the category archive template hierarchy uses the category slug. The slug for the Cat A category is cat-a. Copy the category.php file to category-cat-a.php, change the trace message to In category-cat-a.php as shown below.

<?php
get_header(); 
echo "<p>In category-cat-a.php</p>\n";
echo "<p>Browsing category ", single_cat_title(), "</p>\n";
echo category_description(), "\n";

if (have_posts()) {   // if there are posts
	echo "<table>\n";
	echo "<tr><th>ID</th><th>Title</th><th>Format</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 "</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();
?>

Refresh your browser and note that category-cat-a.php is now being used instead of category-[ID].php. It is unlikely that you would have a need for both of these template patterns for the same category. The important point is that you can have an archive template file specific to a category by using either its ID or its slug.

category-[slug].php is most specific template for categories

The template file category-[slug].php overrides category-[ID].php for category-specific formatting. Here, category-cat-a.php overrides category-11.php for Cat A.

As we demonstrated in this section, the category archive begins with index.php at the root of the entire template hierarchy. If archive.php exists, it will be used instead of index.php whenever an archive page of any type is requested (we will look at the other types of archives in subsequent articles in the series). For category archives, the category.php template file can be used in place of archive.php. For category-specific formatting requirements, category-[ID].php and then category-[slug].php can be used in place of category.php. The diagram below depicts the template hierarchy in the areas that we have investigated so far in the series.

Template Hierarchy with Category Archives

The category archive page hierarchy is rooted on the archive.php template file, which applies to all archives. For category archives, the category.php template can be used. Additionally, you can format individual categories using the category-[id].php and category-[slug].php templates. Click for full-size image.

Hierarchical Categories

WordPress categories can be arranged in a tree-structure, that is, each category can have a parent category. In our discussion of the category archive hierarchy in the last section, the three categories we created were all at the same level in the category hierarchy. Let’s create a few more categories in a tree structure to see how the category archive template hierarchy behaves.

Back on the Administration Screen, navigate to Posts→Categories and create a new category called Cat D with the slug cat-d. Next, create another new category called Cat E with the slug cat-e, but this time set the Parent category to Cat D as shown below.

Category parent assignment

You can assign a parent category to any category to form a category tree. Here, we indicate that Cat E is a child of Cat D.

Finally, create another category Cat F with slug cat-f and parent Cat D. So, categories Cat E and Cat F have Cat D as their parent.

Navigate to the Posts-All Posts page and Quick Edit Post 7 to set its category to Cat D as shown below. Remember to un-check the Uncategorized check box and click Update.

Assigning category to post

When assigning categories to posts, child categories are shown indented below their parents.

Next, edit Post 8 to assign its category to Cat E, un-check Uncategorized, and click Update. Note that we are intentionally not assigning Post 8 to the parent Cat D category. Also, edit Post 9 to assign its category to both Cat D and to Cat F. The final configuration is shown below.

All Posts page showing category assignments

On the All Posts page you can see the category assignments. Here, both Post 7 and Post 9 are assigned directly to the parent category, Cat D. Posts 8 and 9 are assigned directly to the child categories.

Now, visit the site and note that Post 7 is in Cat D, Post 8 is in Cat E, and Post 9 is in both Cat F and its parent Cat D as we assigned.

Home page showing category assignments

The index.php home page list shows the category assignments for our new categories.

Click on the Cat D category next to Post 7. On the category archive page for Cat D, notice that Posts 7, 8, and 9 are included. Even though Post 8 was not explicitly assigned to Cat D, it is still included in the Cat D archive because Cat E is a child of Cat D.

Implicit inclusion of child category posts in parent category archive

When browsing the parent category, Cat D, we see that Post 8 is included even though it wasn’t explicitly assigned to Cat D. That’s because Cat E is a child category of Cat D. If a post is assigned to a child category, it is implicitly included in the parent category archive list.

Click the Back button on your browser and select Cat E next to Post 8. Notice here that only Post 8 is included. Try the same thing with Cat F to see that it only includes Post 9. So if a post is assigned to a child category, it is automatically included in the archive listing of the parent category, whether it has been explicitly assigned to the parent or not. However, the archives of child categories themselves only include the posts assigned to them and not posts assigned to the parent (or sibling) categories.

Display Category List in the Sidebar

In order to demonstrate the category archive template hierarchy in this article we relied on the categories of the posts themselves for links to category archive pages. However, most themes provide for an independent list of categories used by posts. This list is often displayed in a sidebar, usually within a widget. While writing a widget is beyond the scope of this article, I will show you how to easily create a category list in our trace theme’s sidebar.php.

Edit the sidebar.php file to add the call to wp_list_categories() as shown below. The wp_list_categories() call has a ton of parameters to control how and what it outputs. We’ll just use the default options here which outputs a list of categories in an unordered list.

<?php
echo "<p>In sidebar.php</p>\n";
echo "<ul>\n";
wp_list_categories();
echo "</ul>\n";
?>

The screenshot below shows the output from our test database. Note that the child categories are indented in an embedded list. Clicking the category links in this list will navigate to the same URLs as those in the post listing.

Category list

Creating a simple list of categories in the sidebar.php file is as simple as a single function call. The generated URLs are the same as in the post listing above.

In this article we examined the category archive area of the template hierarchy, parent/child category relationships, and category lists. In the next article, we will explore the tag archive hierarchy.

References

Speak Your Mind

*