Stop Category Pages From Showing No Results Message

WordPress Development 3 Comments

If you have a category page which has no posts, it will normally trigger your theme’s no posts found message. Seems sensible! However, I have an edge case where I don’t want that message displayed.

Background

I’m writing a WordPress plugin that manipulates the category page. It appends a list / menu of sub-categories, if they exist, to the category description. Combined with a decent category description, the category page becomes a landing page, rather than just a list of posts.

By default, a category page will list posts coming from that category and from all of the sub-categories. I don’t want the sub-category posts to appear. I want to keep them under them under their sub-category pages, to keeping category pages tightly focussed.

My plugin therefore tells WordPress to ignore sub-category posts on the category page.

For those interested in how I tell WordPress to ignore sub-category posts, I use pre_get_posts to alter the main query. For the code, see my article on setting up a silo structure in WordPress.

The Problem – Unwanted Post Not Found Message

The problem occurs on category pages that don’t have posts, but which have sub-categories with do have posts.

In the model I’m using, it’s perfectly fine for a category page to just show a description and link to sub-categories. It doesn’t matter if it doesn’t have any posts directly under that category.

However, because the query does not return any posts, it fails the if(have_posts()) test and therefore the theme displays a no results message:

Default behaviour with no results message

Kind of spoils the page, doesn’t it?

As mentioned above, WordPress includes posts from sub-categories by default, so this won’t be a problem for most people.

Finding A Solution

Finding a solution was difficult.

If this was just for one site, I could simply edit the theme and remove the no results message. However, I needed a solution I could use within the plugin – and it needed to work with every theme ever built! I can’t ask users to edit their themes to fix this. If I did that, I’d spend the rest of my life doing support.

Some modern themes use: get_template_part(‘no-results’,’archive’); which I may be able to hook into.

However, many themes (including Twenty Ten which I used for the screenshot here) just check if(have_posts()) is true and echo a message if false. There’s no way to hook into that.

So I had to get creative. I didn’t want to leave the message in the source and remove / hide it with JavaScript. I wanted to do something server side to keep it out of the page entirely. I tried everything I (and others) could think of: filtering pre_get_posts, hooking into loop_start, etc. None of it worked.

The only way to make sure the no results message didn’t display was to make sure if(have_posts()) was true. The only way to do that was to trick WordPress into thinking it had found a post.

Solution 1 – Faking A Post Via post_count

It’s actually fairly straight forward to trick WordPress into thinking there is a post when there isn’t. Simply set $wp_query->post_count to 1, like so:

[sourcecode language=”php”]
function siloingpro_no_posts() {
global $wp_query;
if ( is_category() && $wp_query->post_count ##0 ) {
$wp_query->post_count = 1;
}
add_action( ‘wp’, ‘siloingpro_no_posts’ );
[/sourcecode]

I use the wp hook (it’s the first hook after the posts are selected, pre_get_posts is too early) to call the function that will set post_count to 1.

The function grabs the global $wp_query variable, checks that it’s a category page and that there are no posts and, if so, sets post_count to 1. The result?

Screenshot of the not found message having been removed

We’ve gotten rid of the no posts found message, but we now have a different problem: The theme thinks there is a post so it’s trying to display it (see the “Posted on by”). Twenty Ten is actually outputting the following:

[sourcecode language=”html”]
<div id="post-" class="">
<h2 class="entry-title"><a href="" title="Permalink to " rel="bookmark"></a></h2>

<div class="entry-meta">
<span class="meta-prep meta-prep-author">Posted on</span> <a href="" title="" rel="bookmark"><span class="entry-date"></span></a> <span class="meta-sep">by</span> <span class="author vcard"><a class="url fn n" href="http://localhost/china/author/" title="View all posts by "></a></span>
</div><!– .entry-meta –>

<div class="entry-summary">
</div><!– .entry-summary –>

<div class="entry-utility">
<span class="comments-link"><span>Comments Off</span></span>
</div><!– .entry-utility –>
</div><!– #post-## –>
[/sourcecode]

Fair enough. But now we need a plan B.

What Didn’t Work

Once again, I tried everything I could think of to make WordPress forget the post once the if(have_posts()) check was complete.

I tried hooking into loop_start and setting post_count back to 0, but it was too late. I tried to break out of the loop via loop_start, but it would’t let me. Using die was a little over the top – it stopped the loop, but it also stopped the sidebars and the footers!

I thought about trying to filter the_permalink, the_title, the_title_attribute, the_ID, the_content and every other function a theme might be calling at this case, but apart from being impractical it just wouldn’t work. Once the theme has passed the if(have_posts()) check, it’s going to output some HTML one way or another.

Solution 2 – Using Output Buffering To Prevent The Loop Output

There was one way left to prevent any output via PHP: output buffering.

Your first thought may be “can’t you use output buffering to prevent the ‘no results’ output?”. No. There are no hooks to start and stop the output buffering if there are no posts (unless I was to buffer everything).

If WordPress thinks there are posts however, then we can use the loop_start and loop_end hooks to start and stop the buffering. That allows us to catch everything in the loop, which in this case is our ghost post entry. We can then discard it, preventing it from being output. Here’s how.

First, I edited the code above to add a global variable $siloingpro_ob_required that indicates whether we need to buffer the loop, ie if it’s a category page with no posts:

[sourcecode language=”php”]
$siloingpro_ob_required = 0;
function siloingpro_no_posts() {
global $wp_query, $siloingpro_ob_required;
if ( is_category() && $wp_query->post_count ##0 ) {
$wp_query->post_count = 1;
$siloingpro_ob_required = 1;
}
}
add_action( ‘wp’, ‘siloingpro_no_posts’ );
[/sourcecode]

Next I hook into loop_start and turn on output buffering using ob_start (if we’ve decided it’s required), then I hook into loop_end and turn it off using ob_end_clean:

[sourcecode language=”php”]
// Start buffering before loop, if it’s a category page with no posts
function siloingpro_ob_start() {
global $siloingpro_ob_required ;
if ( is_category && $siloingpro_ob_required )
ob_start( );
}
add_action( ‘loop_start’, ‘siloingpro_ob_start’ );

// Stop buffering (and throw away content) after loop, if it’s a category page with no posts
function siloingpro_ob_end() {
global $siloingpro_ob_required ;
if ( is_category && $siloingpro_ob_required )
ob_end_clean();
}
add_action( ‘loop_end’, ‘siloingpro_ob_end’, 1000 );
[/sourcecode]

It’s that simple! If I use ob_end_flush instead of ob_end_clean it will output the loop contents, but by using ob_end_clean it will simply discard it. Voilà:

Screenshot of final solution, with no problems

But Isn’t Output Buffering Bad?

In general, output buffering should be avoided in WordPress plugins, for a number of reasons.

Output buffering can interfere with what other plugins are trying to do and cause them to misbehave. In this case, any modifcations made by plugins that filter the_content or the_title etc, will be lost. No problem as there aren’t any real posts for them to filter.

It can also cause collisions with other output buffering, which could cause all sorts of unexepected stuff. In this specific case, the risk is minimal. Output buffering is nested, which means if another plugin is turning output buffering on before I do and off after I do, there’s no problem. Its only a problem if they turn it on before I do, but turn it off before I do. Not impossible, but unlikely given that it’s only on during the loop.

As far as performance goes, the impact should be minimal. It’s actually far better than the overhead of creating a new query, which is the only other way to solve this (leave the subcategory posts in the main loop and create a secondary loop without them to display).

And remember, this is only happening on category pages with no posts.

Acknowledgements

A big thanks goes to the following people who all made suggestions or offered to help:

You guys rock!

Final Thoughts

Although this solution seems to work really well, I’m still slightly uncomfortable about using output buffering, so let me know if there is a better way to solve this!

3 responses on “Stop Category Pages From Showing No Results Message

  1. Ben May

    Ah, This makes a bit more sense than on Twitter! Yeah, I’ve had a similar problem in the past with taxonomy archives I think. I can’t remember off the top of my head what I did to get around it!

  2. Stephen Cronin Post author

    Hi Ben,

    Yeah, it’s pretty hard to describe on Twitter. That’s why I promised to write the problem up later. But that Ryan McCue kept nagging and nagging until I came up with the solution above, so I didn’t need to write up the actual problem… 🙂

  3. Mehedi Hasan

    How can I show a custom message instead of a blank page for empty categories? Please Help.