Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I'm using WP_Query to create 'latest posts' & 'popular posts' sections on my home page. I'm trying to just pull 5 posts from 2 categories (9 & 11) but it will only show posts from cat 9.

Here's the php I'm using for 'latest posts'-

<ul>
        <?php 

        $cat = array(9,11);
        $showposts = 5;
        $paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
        $args=array(
            'category__in' => $cat, 
            'showposts' => $showposts,
            'paged' => $paged,
            'orderby' => 'post_date',
            'order' => 'DESC',
            'post_status' => 'publish',
           );

        $the_query = new WP_Query ( $args ); //the query


        $i = 0;while ($the_query->have_posts() ) : $the_query->the_post(); //start the loop
        ?>

        <?php
        if($i==0){ //Sets the output for the top post
        ?>  
            <li class="first-news">
                <div class="post-thumbnail"><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail(350,187); ?></a></div>
                <h2 class="post-box-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                <p class="post-meta"><span class="tie-date"><?php the_time('F jS, Y') ?></span></p>
                <div class="entry"><?php the_excerpt(); ?></div>
                <div><a class="more-link" href="<?php the_permalink(); ?>">Read More »</a></div>

            </li>


        <?php
            $i++;
             } else { ?>

                <li class="other-news rar">
                <div class="post-thumbnail"><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail(145,93); ?></a></div>
                <h3 class="post-box-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
                <p class="post-meta"><span class="tie-date"><?php the_time('F jS, Y') ?></span></p>
           </li>

        <?php } endwhile; //end of the loop ?>
        <?php wp_reset_postdata(); // reset the query ?>
        </ul> 

Any suggestions?

share|improve this question
    
Hm, that looks like it should work. What happens if you try to query only category 11? –  Rarst Feb 2 at 13:24
1  
Please also check not just querying 5 posts - it could be that the first five are only from category 9. –  fischi Feb 2 at 14:19
1  
'showposts' is deprecated. Try using 'posts_per_page' instead. –  Mayeenul Islam Feb 2 at 15:23
    
Oh =/ posts_per_page wasn't working for me –  user3205234 Feb 3 at 14:12
    
When things are deprecated does that mean that it'll stop working in future Wordpress updates? –  user3205234 Feb 3 at 14:12

3 Answers 3

According to the Codex:

Show Posts From Several Categories (Display posts that have these categories, using category id) would be:

$query = new WP_Query( 'cat=9,11' );

And, the Codex's Pagination Parameters says that, 'showposts' is deprecated and replaced with 'posts_per_page'.

posts_per_page (int) - number of post to show per page (available with Version 2.1, replaced showposts parameter).

share|improve this answer
    
Thanks for your answer, I'll give that a go after I've tried the one from s_ha_dum. (posts per page wasn't really working for me before for some odd reason) –  user3205234 Feb 3 at 14:08
    
@user3205234: You should be using posts_per_page but it won't solve the problem I think you are having-- that one category or the other fills up your post limit before the other is represented. –  s_ha_dum Feb 3 at 14:11

In my experience using 'posts_*' filters ('posts_request', 'posts_where'...) in combination with str_replace / preg_replace is unreliable and unflexible:

Unreliable because if another filter modify uses one of that filters, in better case one gets unexpected results, in worst cases one gets SQL errors.

Unflexible because changing an argument, e.g. 'include_children' for categories, or reuse the code for e.g. 3 terms instead of 2 need a lot of works.

Moreover, adapt code to be musltisite compatibe need to edit SQL manually.

So, sometimes, evevn if not the best solution regarding performance, a more canonical approach is the best and more flexible one.

And performance can be improced with some caching tricks...

My propose:

  1. write a function taht make use of usort to order posts coming from different queries (e.g. one per term)
  2. write a function that on first run run separate queries, merge results, order them, cache them and return them. On subsequent request simply return the cached results
  3. handle caching invalidation when needed

Code

First the function that order posts:

function my_date_terms_posts_sort( Array $posts, $order = 'DESC' ) {
  if ( ! empty( $posts ) ) {
    usort( $posts, function( WP_Post $a, WP_Post $b ) use ( $order ) {
      $at = (int) mysql2date( 'U', $a->post_date );
      $bt = (int) mysql2date( 'U', $b->post_date );
      $orders = strtoupper($order) === 'ASC' ? array( 1, -1 ) : array( -1, 1 );
      return $at === $bt ? 0 : ( $at > $bt ) ? $orders[0] : $orders[1];
    } );
  }
  return $posts;
}

Then the function that get non cached results:

function my_fresh_terms_get_posts( $args, $terms, $tax_query_args = NULL ) {
  $posts = array();
  // we need to know at least the taxonomy
  if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
  // handle base tax_query
  $base_tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
  // run a query for each term
  foreach ( $terms as $term ) {
    $term_tax_query = wp_parse_args( array(
      'terms' => array( $term ),
      'field' => is_numeric( $term ) ? 'term_id' : 'slug'
    ), $tax_query_args );
    $args['tax_query'] = array_merge( $base_tax_query, array($term_tax_query) );
    $q = new WP_Query( $args ); 
    if ( $q->have_posts() ) {
      // merging retrieved posts in $posts array
      // preventing duplicates using ID as array keys
      $ids = wp_list_pluck( $q->posts, 'ID' );
      $keyed = array_combine( $ids, array_values( $q->posts ) );
      $posts += $keyed;
    }
  }
  return $posts;
}

Now the function that check cache and return it if available or return non cached results

function my_terms_get_posts( $args, $terms, $tax_query_args = NULL, $order = 'DESC' ) {
  // we need to know at least the taxonomy
  if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
  $tax = $tax_query_args['taxonomy'];
  // get cached  results
  $cached = get_transient( "my_terms_get_posts_{$tax}" );
  if ( ! empty( $cached ) ) return $cached;
  // no cached  results, get 'fresh' posts
  $posts = my_fresh_terms_get_posts( $args, $terms, $tax_query_args );
  if ( ! empty($posts) ) {
    // order posts and cache them
    $posts = my_date_terms_posts_sort( $posts, $order );
    set_transient( "my_terms_get_posts_{$tax}",  $posts, DAY_IN_SECONDS );
  }
  return $posts;
}

Cache is auto-cleaned daily, however, is possible to invalidate it everytime a new post in on a specific taxonomy is added or updated. That can be done adding a cleaning cache function on 'set_object_terms'

add_action( 'set_object_terms', function( $object_id, $terms, $tt_ids, $taxonomy ) {
  $taxonomies = get_taxonomies( array( 'object_type' => array('post') ), 'names' );
  if ( in_array( $taxonomy, (array) $taxonomies ) ) {
    delete_transient( "my_terms_get_posts_{$taxonomy}" );
  }
}, 10, 4 );

Usage

// the number of post to retrieve for each term
// total of posts retrieved will be equat to this number x the number of terms passed
// to my_terms_get_posts function
$perterm = 5;

// first define general args:
$paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
$args = array(
  'posts_per_page' => $perterm,
  'paged' => $paged,
);

// taxonomy args
$base_tax_args = array(
  'taxonomy' => 'category'
);
$terms = array( 9, 11 ); // is also possible to use slugs

// get posts
$posts = my_terms_get_posts( $args, $terms, $base_tax_args );

// loop
if ( ! empty( $posts ) ) {
  foreach ( $posts as $_post ) {
    global $post;
    setup_postdata( $_post );
    //-------------------------> loop code goes here    
  }
  wp_reset_postdata();
}

Functions are flexible enough to use complex queries, even queries for additional taxonomies:

E.G.

$args = array(
  'posts_per_page' => $perterm,
  'paged' => $paged,
  'post_status' => 'publish',
  'tax_query' => array(
     'relation' => 'AND',
     array(
       'taxonomy' => 'another_taxonomy',
       'terms' => array( 'foo', 'bar' ),
       'field' => 'id'
     )
   )
);

$base_tax_args = array(
  'taxonomy' => 'category',
  'include_children' => FALSE
);

$terms = array( 'a-category', 'another-one' );

$posts = my_terms_get_posts( $args, $terms, $base_tax_args );

In this way the 'tax_query' set in $args array will be merged dinamically with the tax query argument in $base_tax_args, for each of the terms in the $terms array.

Is also possible order posts in ascending order:

$posts = my_terms_get_posts( $args, $terms, $base_tax_args, 'ASC' );

Please note:

  1. IMPORTANT: If some posts belong to more than one category among the ones passed to function (e.g. in OP case some posts have both category 9 and 11) the number of post retrieved will be not the one expected, because function will return that posts once
  2. Code needs PHP 5.3+
share|improve this answer

What you are asking is a near duplicate of this question: How do I create my own nested meta_query using posts_where / posts_join?

The problem, as @fischi suggests, is almost certainly that results are coming from one category or the other and hitting the post limit before both are equally represented. To make this work, you need a UNION. WP_Query is not capable of that logic, but with hooks you can make it work.

$cat = 9; // your first category
$showposts = 5; // actual results are twice this value
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args=array(
  'cat' => $cat, 
  'posts_per_page' => $showposts,
  'paged' => $paged,
  'orderby' => 'post_date',
  'order' => 'DESC',
  'post_status' => 'publish',
);

function create_cat_union($clauses) {
  remove_filter('posts_request','create_cat_union',1);
  $clauses = str_replace('SQL_CALC_FOUND_ROWS','',$clauses,$scfr);

  $scfr = (0 < $scfr) ? 'SQL_CALC_FOUND_ROWS' : '';

  $pattern = 'wp_term_relationships.term_taxonomy_id IN \(([0-9,]+)\)';
  $clause2 = preg_replace('|'.$pattern.'|','wp_term_relationships.term_taxonomy_id IN (11)',$clauses); // manipulate this

  return "SELECT {$scfr} u.* FROM (({$clauses}) UNION ({$clause2})) as u";
}
add_filter('posts_request','create_cat_union',1,2);

$the_query = new WP_Query ( $args ); //the query
var_dump($the_query->posts);

A couple of notes: WP_Query will parse the category argument and find category children so the first UNION will include all of the children of category 9. I did not duplicate that logic for category 11. If you need that functionality, you can refer to the WP_Query source and reproduce the effect.

share|improve this answer
    
Oh, sorry I should have looked harder to find the answer =/ –  user3205234 Feb 3 at 14:05
    
No apology necessary. The question referred to would have gotten you close, and if your PHP/MySQL are good you could have gotten to the answer, but it is not a true duplicate. –  s_ha_dum Feb 3 at 14:07
    
Thank you for your answer, I will give it a try. Not hear of UNION before, something new for me to look into. Although the newest 5 posts are definitely from both categories. –  user3205234 Feb 3 at 14:07
    
Ahh, my MySQL isn't too good yet- got a lot to learn! Thanks for your help =) –  user3205234 Feb 3 at 14:09
    
Even if the "newest five" are from both categories what you would get without the union is 3 of one and 2 of the other, or 4 of one and 1 of the other-- that is not an even return from each. –  s_ha_dum Feb 3 at 14:55

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.