A normal category page
I don’t know if you’ve noticed: on a WordPress category archive page, posts from that category and all its child categories are displayed. I don’t know why. It’s not the behaviour I would have expected, and although it’s not necessarily a bad thing, I can understand that some people wouldn’t want it that way.
“Solutions”
I used to have a nice little function that used the pre_get_posts
filter to exclude all the child categories, but for some reason that function stopped working somewhere between WordPress 2.7 and 2.8 and I couldn’t for the life of me get it to work again, at least not the way I wanted.
Furthermore, a simple custom query like the one in my loop tutorial doesn’t work quite right; often it modifies the query so that it thinks it’s on a normal home/blog page (very noticeable if you use only titles, or excerpts on archive pages and not the home page).
category__in
For those in-the-know, you might be thinking, just use category__in
: it gets the posts from that category ID and not the child categories. However, when I tried it, I didn’t like the results.
It turned out that the reason the results were a little funky is because setting category__in
obliterates the cat
query variable. Two specific impacts of that are that the single_cat_title()
template tag doesn’t work (because it uses get_query_var('cat')
to get the category title) and any plugin using get_query_var('cat')
(including Yoast Breadcrumbs) won’t work.
I tried setting the cat
variable along with category__in
, but cat
seemed to override such that the child categories were no longer excluded and adding category__not_in
didn’t help either.
The real solution
The only real solution I could come up with was to use category__in
before the loop and then reset the cat
variable after the loop to allow plugins and widgets to use it.
Before your category archive loop
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$current_cat = get_query_var('cat');
$args=array(
'category__in' => array($current_cat),
'paged' => $paged
);
query_posts($args);
Note that the $paged
bit is essential for maintaining pagination. I usually prefer to use $query_string
in custom queries for that, but it doesn’t work in this case.
After the category page loop
Assuming the $current_cat
variable is still in tact from before the loop:
set_query_var("cat",$current_cat);
Otherwise use category__in
to figure out what category you’re in:
$cat = get_query_var('category__in');
set_query_var("cat",$cat[0]);
Note that category__in
returns an array, so we need to use $cat[0]
to get the first array value, which is the ID of the current category.
Now the cat
query variable will be available for use before and after your loop. It won’t be available inside the loop, but after two days on the topic, I couldn’t find a way around that — if you do, let me know!
Modifying your Thesis category archive pages
If you would like to use this method in Thesis you need to insert the code before and after the loop with PHP via custom_functions.php.
Before Thesis content
This method presents another issue, because Thesis uses single_cat_title()
in the archive info. So, when we set category__in
the category name in the archive info will be blank. The following code initiates the custom query, removes the default archive info and adds a working archive info back in.
function remove_child_cats() {
if (is_category()) :
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$current_cat = get_query_var('cat');
$args=array(
'category__in' => array($current_cat),
'paged' => $paged
);
query_posts($args);
remove_action('thesis_hook_archive_info', 'thesis_default_archive_info');
echo ' <div id="archive_info">' . "\n";
?>
<p><?php _e('From the category archives:', 'thesis'); ?></p>
<h1><?php echo get_cat_name($current_cat); ?></h1>
<?php
echo ' </div>' . "\n";
endif;
}
add_action('thesis_hook_before_content','remove_child_cats');
After Thesis loop
function reset_cat_var() {
$cat = get_query_var('category__in');
set_query_var("cat",$cat[0]);
}
add_action('thesis_hook_after_content','reset_cat_var',1);
That’s it: a way to exclude child categories from category archive pages without breaking (too much) stuff.
Please feel free to ask (related) questions!
Craig Sunney says
Genius
gorostas says
Hi,
great post for first, I just wanted to say if you know for that plugin just_one_cat.php makes this parent cat problem with something like this:
class Just_One_Category { var $q; function get_posts( &$q ) { $this->q =& $q; if ( !$q->is_category ) return false; add_action( 'posts_where', array( &$this, 'where' ) ); } function where( $where ) { global $wpdb; remove_action( 'posts_where', array( &$this, 'where' ) ); $cat = rtrim( $this->q->get( 'cat' ), ' /' ); if ( !is_numeric( $cat ) || $cat < 1 ) return $where; $field = preg_quote( "$wpdb->term_taxonomy.term_id", '#' ); $just_one = $wpdb->prepare( " AND $wpdb->term_taxonomy.term_id = %d ", $cat ); if ( preg_match( "#AND\s+$field\s+IN\s*\(\s*(?:['\"]?\d+['\"]?\s*,\s*)*['\"]?\d+['\"]?\s*\)#", $where, $matches ) ) $where = str_replace( $matches[0], $just_one, $where ); else $where .= $just_one; return $where; }}function joc_init() { $just_one_category = new Just_One_Category; add_action( 'pre_get_posts', array( &$just_one_category, 'get_posts' ) );}add_action( 'init', 'joc_init' );
but youre solution is more flexible
Sirkka says
Smart tip there.
greg says
This is a gruesome WordPress bug that makes subcategories in default WP unusable in my opinion!
Thank you for posting this, I spent several hours looking for this answer today, and then several hours trying to code it myself. I came up with a solution, but it was messy and annoying.
kristarella says
Greg — You’re welcome. Yeah, I spent a couple of days on this: not a full two days, but decent chunks over a couple of days before I figured out exactly what was happening where and how I could combat it… figured the least I could do was share!
Adam Baird says
You’re a lifesaver…actually looking to combat this exact problem this morning. Thanks!
Michael Krapf says
Yes!!! Thank you 😉
Justin Bartlett says
Is there a way to sort category teasers by a custom field – e.g. If all of the teasers of a category were an Event, for example, and each event has a “_date” custom field, how would I sort the teasers by the date of the event?
All that I could find on possibly doing this was a code snippet posted online regarding the wordpress post query loop:
query_posts(‘meta_key=_date&meta_compare=<=&meta_value=20&orderby=meta_value&order=DESC');
And I guess my brain just can't make the connection between this code tidbit and how I'd implement it in Thesis.. Figured I'd ask the expert!
kristarella says
Justin — Assuming that bit of code works to modify the query as-is, you can use it in Thesis by hooking it before the content. I.e.,
Justin says
I was taking a look at the Thesis core files and I didn’t see any instances of “query_posts” so I’m pretty sure I’m … well… lost! I placed the bit of code into custom functions and of course it doesn’t modify the query as is. Oh boy! I have no idea whether to simply have the category page display nothing, and re-write a post loop that in effect creates teasers that I can then order by date or what to do! If you absolutely had to order a bunch of event posts by the date of the event how would you start to tackle it?
kristarella says
Justin —
query_posts()
can be used to create a query for a loop, but each page in WordPress already has a default query (e.g., the home page displays recent posts, category pages show recent posts from that category, single posts or pages etc), so Thesis doesn’t need to use the function. We can use it to modify the default query though.I have a feeling that your custom query doesn’t make sense. Basically what your query is saying is “show posts with the meta key ‘_date’ and whose meta value of that key is less than 20, order those posts in descending order according to that meta key”. You probably only need
query_posts('meta_key=_date&orderby=meta_value&order=DESC');
.Note that this modifies an existing query. Does this category page you’re referring to already have posts on it? If not you will have to write a whole custom loop. I don’t really have the full story on the custom field and why and how you’re using it, so I don’t think I can say much more, but I thought I’d just put it out there that you can change the publish dates of posts and pages, you might be able to use that to suit your purpose.
Justin says
Oh boy if this were a personal project I would have definitely taken a different approach and probably not given two hoots about chronological order of events in the first place. This is a case of Justin says to a friend, “Yes we can arrange those posts chronologically” (thinking, well, posts are naturally displayed chronologically), and “yeah bro I can make it easier for your users to add events by creating meta boxes”.
Liittle did I know this would translate to – the custom field dates are going to determine the post order.
I guess this all translates to I really should have looked into feasibility before I took the project!
My original idea (to save time customizing) was to simply have the category page events and all subcategories of events display teasers, and that these teasers would be sorted by the custom field date.
Interestingly, when I changed the Thesis settings back to displaying full posts on the category pages (same as home page, then selected the highest number of featured posts for no teasers), the query post function worked perfectly and sorted my posts by the date field!! ???? !!!
I’m not sure why this worked. But the fact that it did work makes me a very happy Justin! And not looking like an dolt in front of a friend – priceless!
So a little bit of conditional css based on the current category hiding .. eh.. everything but the headline, and adding back in the event date and a thumnail image – voila! I’ll soon (fingers crossed) have custom event category pages and subpages that actually sort by the date of the event, rather than the date of posting.
Code bloated? Probably. Roundabout / is there probably a more direct way to do this? Most definitely. Happy I can get it done — heck yeah.
I’m definitely not a programmer, and I’m thankful there’s awesome people with real smarts like you to help us non-tech savy, “it magically works now but I don’t understand it” types out!
Gabriel says
Hello Kristarella,
I’m trying to add a category title to each category without manually adding one in.
What I’d like to do is add the code with different text then “currently browsing” how-ever I’m trying to exclude certain categories. I am editing the archive page where the categories are displayed.
Your help is much appreciated.
Gabriel says
The code was hidden. Here it is again: <?php single_cat_title(‘Currently browsing’); ?>
kristarella says
Gabriel — What theme are you using?
If you want to use text different to “Currently browsing” then just change those words in the code you posted.
Use conditional tags to restrict which categories it shows on. E.g., to NOT display the title on archive pages of the category Books and the category Technology:
Gabriel says
Hi,
I’m using a premium wordpress theme from studiopress.com
I know I can change that text but let me explain a bit more. I’ve been very unclear in my initial post. Right now I have about 100 categories. Example: books, magazines, pdf … I want to add another string into the archive page for seo purposes. For example every page would say “Read (category) Free Online.” There are two category archives where I don’t want anything to be displayed as they are general categories.
I’ve tried using your code above but I get a “Parse error: syntax error, unexpected T_STRING in…” Error.
What else can you recommend?
I
kristarella says
Gabriel — That is a PHP error and should be pretty easy to fix. Take a closer look at the error, it should tell you which line of which file the error is on. Unexpected T_STRING will generally mean you’ve forgotten some character before that bit, like a closing bracket or a semi-colon.
Gabriel says
Hi,
Well the issue is with the 15th string. What I did was just pasted exactly what you typed in to test and see if it worked and get this error right away.
Here’s the code:
<?php get_header(); ?>
<div id="content">
<div id="contentleft">
<div class="postarea">
<?php include(TEMPLATEPATH."/breadcrumb.php");?>
<div class="cat_description">
<?php
if (!(is_category(array(9,’blue-cheese’,’Stinky Cheeses’))
single_cat_title(‘You’re looking at posts from the category’);
?>
kristarella says
Gabriel — As I mentioned, the error usually comes from something missing before the line with the error. You’re missing two closing parentheses at the end of line 14.
Gabriel says
Hi,
Even when I add them I still get an error.
I changed it to the code that you had suggested just to test it and i continue to get the error. Here’s a link to the whole archive php code.
http://tinypaste.com/4b6bd3
kristarella says
Gabriel — Ok, that was the first error, then I made an error. It needs to be
I shouldn’t have used single quote marks around the prefix when there was a single quote/apostrophe in it.
Gabriel says
Thank you very much, that works perfectly now. 🙂
Enjoy the rest of your evening / night.
Joel says
I was using a plug in for this – and it doesn’t work with WP 3.1
Once I put the code in custom_functions, do I do anything else? I think it works for some category pages but not all – I’m still investigating.
Thanks
kristarella says
Joel — If you’ve got both of the last chunks of code in your functions you shouldn’t need to do anything else.
Craig says
@Joel….It’s still working for me after the upgrade to 3.1. Check this link to the finance category on this test site which also has child categories which are not showing: http://help-hub.com/c/finance/insurance-finance/
Joel says
Ah ha – there is a conflict of some kind with other customization I added for this category. For a couple of category pages on our site (including this “Our Designers”) I have some customization to display only teasers. When I take that category ID out of the code, I get only the posts for the parent category – which is what I want. But now I get one “feature post” then teasers – which I don’t want.
Thanks you for the quick replies!
@Craig – I haven’t had a chance to look at your site yet but were you using “Just one Category”? Once I upgraded things went a south (no offense Kritarella!)
kristarella says
Joel — What was the other customisation that wasn’t compatible with this? Perhaps we can find a way to integrate them.
Joel says
Should I send the code or post it? It makes all posts on specified categories pages teasers.
Joel says
I’ll go ahead and post it – thank you for taking a look!
Here is the code that is supposed to show posts as teasers only for the specified category (I don’t recall where I stole it) It’s in custom_functions.php, just before the parent only code.
/*******************************************************************************
* show only teasers in selected category pages
*******************************************************************************/
$ttw_alternate_category_loop = new ttw_custom_category_loops;
class ttw_custom_category_loops extends thesis_custom_loop {
function category(){
$alternate_categories = array(‘141′,’79’); //specify categories as list: = array(‘4′,’6’);
if(is_category($alternate_categories)){
thesis_archive_intro();
$paged = (get_query_var(‘paged’)) ? get_query_var(‘paged’) : 1; // necessary for paging
$post_cut_off = 10; // set the number of posts to show per page
$ttwcat_current_cat = get_query_var(‘cat’); // necessary for the other functions below
$teaser_count = 1;
query_posts (array( // New query vars
‘posts_per_page’ => $post_cut_off,
‘paged’ => $paged,
‘cat’ => $ttwcat_current_cat)
);
while (have_posts()) { #wp
the_post(); #wp
if (($teaser_count % 2) == 1) {
$top = ($post_count == 1) ? ‘ top’ : ”;
$open_box = "\t\t\t<div class=\"teasers_box$top\">\n\n";
$close_box = ”;
$right = false;
}
else {
$open_box = ”;
$close_box = "\t\t\t</div>\n\n";
$right = true;
}
if ($open_box != ”) {
echo $open_box;
thesis_hook_before_teasers_box($post_count);
}
thesis_teaser($classes, $post_count, $right);
if ($close_box != ”) {
echo $close_box;
thesis_hook_after_teasers_box($post_count);
}
$teaser_count++;
}
if ((($teaser_count – 1) % 2) == 1)
echo "\t\t\t</div>\n\n";
}
else{
thesis_loop::category();// Use the default category loop
}
}
}
kristarella says
Joel — Looks to me like if you used
category__in
instead ofcat
in your query, then that would fix your problem?Joel says
Bingo! That did it.
Thanks very much for the help.
Jennifer Heller says
Hey Kristarella,
Thanks for this and for your many other great tutorials. We want to use Thesis’ Introductory Headline and Introductory Content above our individual category archives. Somehow this stops working when I implement your code, even if I take out:
remove_action(‘thesis_hook_archive_info’, ‘thesis_default_archive_info’);
echo ‘ ‘ . “\n”;
?>
<?php
echo ' ‘ . “\n”;
I’ve tried combing through the code to find out how to call those variables to no luck. Can you help?
Thank you!!
Fatih Toprak says
Hey Kristarella,
For example;
video cat id = 4 video cat child category id’s = 7,8,9
news cat id = 5 news cat child category id’s = 11,12,13
I wanna display choosen categories ( like video and news ) in the different archive.php?
Is it possible thanks.
( sorry about my bad Eng )
p.s. could you write example loop for this please
kristarella says
Jennifer — I’m not sure what the problem is, I don’t think the code you posted came through properly. Perhaps if you put your custom_functions.php file in Pastebin for me I could see what’s wrong.
Faith — I’m not sure what you mean, but perhaps this post on loops will help you.
Kyathi says
Nice post
Jennifer says
Thank you Kristerella for the response. I actually got this fixed via the forums. You were a great help there too. Thanks again.
Robert Lovrekovic says
Thank you thank you thank you!!!! I tried every other solution and non work. I lost over 5 hours to figure that out. This is the only solution that worked perfectly. Thank you
Continue with a great job, well done.
Mohamamd says
Where should I exactly put the 2 codes? function?
I’m using a custom theme.
Thanks,
kristarella says
Mohamamd — It depends on the theme. I’ve shown how to do it in Thesis, other themes might have a similar method using functions.php, or you might have the edit the category.php theme file.
serge says
Hi Kristen, saw your little snippet on pre_get_posts stopped working back in 2.8 ? I have coded it back – see here http://thesistut.com/2012/excl.....-category/
cheers,
serge
Steve says
Hi,
I’ve implemented this code and it’s working great, only problem is it has stopped the ‘Search’ feature working. I know the search is finding results, but it’s not outputting them, I can also see that the search uses the loop.php code to display the results. I think I need to put a conditional statement around the this code so the search will not use the category restriction, but I’ve no idea how to do this, any suggestions appreciated.
Thank you.
serge says
Hi Steve, try the code from the link in my previous comment (#40) . It does not interfere anywhere else except for the targeted category pages.
Cheers,
Serge