1

I'm trying to create a feed where posts are pulled based off a set of authors, OR a set of categories, but I haven't had much luck. I've tried using meta_query and tax_query, but neither worked. Basically the query I'm trying to run is:

"SELECT * FROM wp_posts WHERE author_id = $set_of_authors OR category = $set_of_categories"

I'm hoping to get a post object that I could just run through the loop normally, but I'll take what I can get. The only thing left I could think of was to run get_results with that query, but I know this is generally not the WordPress way. Is there a better way to get this query?

  • Did you succeed with this problem? – birgire Nov 4 '15 at 13:20
  • @birgire I did, although I'm not sure it's the best possible answer. I posted it below. – Billy Brawner Nov 5 '15 at 17:38
  • ok, I will see if I can construct an alternative solution, probably tomorrow. – birgire Nov 5 '15 at 17:40
1

Custom feed slug

To create a custom feed slug, e.g.

example.tld/billyfeed/

we can use the add_feed() but we have to flush the rewrite rules to activate it. We can do that e.g. by visiting the Permalinks settings page in the backend.

To generate the feed we can use e.g. do_feed_rss2() or do_feed_atom().

Here's an example:

add_action( 'init', function()
{
    add_feed( 'billyfeed', function()
    {
        do_feed_rss2();
    });
});

Modify the custom feed query

Next we need to modify our custom feed so that we either get the authors or categories.

Here's a workaround using the posts_clauses filter but without using string replacements with str_replace().

/**
 * Custom feed example.tld/billyfeed that queries authors or categories
 * Supports pagination
 *
 * @link http://wordpress.stackexchange.com/a/207876/26350
 */
add_filter( 'posts_clauses', function( $clauses, \WP_Query $q ) 
{    
    //---------------------------------------
    // Only target our custom feed
    //---------------------------------------
    if( ! $q->is_feed( 'billyfeed' ) || ! $q->is_main_query() )
        return $clauses;

    global $wpdb;       

    //---------------------------------------
    // Input - we edit this to our needs
    //---------------------------------------
    $tax_query = [ 
        [
            'taxonomy' => 'category',
            'field'    => 'slug',
            'terms'    => [ 'red', 'green', 'blue' ]
        ]
    ];      
    $author__in = [ 1, 2, 3 ];  

    //---------------------------------------
    // Generate the tax query SQL
    //---------------------------------------
    $qv = [ 'tax_query' => $tax_query, 'cat' => null, 'tag' => null ];
    $q->parse_tax_query( $qv );
    $tc = $q->tax_query->get_sql( $wpdb->posts, 'ID' );

    //---------------------------------------
    // Generate the author query SQL
    //---------------------------------------
    $csv     = join( ',', wp_parse_id_list( (array) $author__in ) );
    $authors = " {$wpdb->posts}.post_author IN ( $csv ) ";

    //---------------------------------------
    // Make sure the authors are set and 
    // the tax query is valid (doesn't contain 0 = 1)
    //---------------------------------------
    if( ! empty( $author__in ) && false === strpos ( $tc['where' ], ' 0 = 1' ) ) 
    {
        // Append to the current join/where parts
        $clauses['join']  .= $tc['join'];
        $clauses['where'] .= sprintf( 
            ' AND ( %s OR ( 1=1 %s ) ) ', // The tax query SQL comes prepended with AND
            $authors,
            $tc['where']
        );
    }

    return $clauses;   

}, PHP_INT_MAX, 2 );

where we modify the $tax_query and $author__in to our needs.

This generates the following main SQL query:

SELECT SQL_CALC_FOUND_ROWS wp_posts.ID 
FROM wp_posts  
INNER JOIN wp_term_relationships 
    ON (wp_posts.ID = wp_term_relationships.object_id) 
WHERE
    1=1  
    AND wp_posts.post_type = 'post' 
    AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') 
    AND (  wp_posts.post_authors IN (1,2,3 )  OR ( 1=1  AND ( 
        wp_term_relationships.term_taxonomy_id IN (1,43,120)
    ) ) )   
ORDER BY wp_posts.post_date DESC 
LIMIT 0, 10;

With this approach we can still paginate our feed:

example.tld/billyfeed/?paged=2
  • Thank you for taking the time to do this! Would this be dynamic, though? – Billy Brawner Nov 7 '15 at 14:32
  • You're welcome. What do you mean by dynamic? It's dynamic in the sense that if you publish more posts, satisfying the conditions here, they will be added to the feed stream. Just be aware of the browser caching when viewing the WordPress feeds @BillyBrawner – birgire Nov 7 '15 at 14:42
  • Well, in this case, I need this code because I'm working on a BuddyPress "plugin" that allows users to subscribe to authors and/or categories, which creates a personal feed for them in their profile made up of all the latest posts from each of their subscriptions. With the code that I wrote, any subscriptions/unsubscriptions are reflected immediately in their personal feed and are added or removed accordingly. Would the same happen here with the code you've provided? – Billy Brawner Nov 7 '15 at 15:33
  • Here I had a single-site setup in mind, with a single public feed. For a multisite this would need a different approach, since we don't have, by default, a single table with all posts from all sites. But I'm not sure what's the best approach with BuddyPress. But (almost) everything is possible ;-) @BillyBrawner – birgire Nov 7 '15 at 16:54
0

Well, it's not pretty and I'm sure there are better ways to do this, but this is what I came up with:

if ($author_ids) {
    $author_ids = implode(',', $author_ids);
}
if ($cat_ids) {
    $cat_ids = implode(',', $cat_ids);
}
global $wpdb;
if (count($author_ids) > 0 && count($cat_ids) > 0) {
    $posts = $wpdb->get_results(
        "SELECT *
        FROM $wpdb->posts
        INNER JOIN $wpdb->term_relationships
        ON $wpdb->posts.ID = $wpdb->term_relationships.object_id
        WHERE post_status = 'publish'
        AND post_author
        IN ($author_ids)
        OR term_taxonomy_id
        IN ($cat_ids)
        ORDER BY post_date DESC"
    );
  $post_ids = array();
  foreach ($posts as $post) {
    if (!in_array($post->ID, $post_ids)) {
        $post_ids[] = intval($post->ID);
    }
  }
    $post_ids = array_slice($post_ids, 0, 12);
    $query = new WP_Query(array('post__in' => $post_ids));
} else if (count($author_ids > 0 ) && count($cat_ids) == 0) {
    $query = new WP_Query("author={$author_ids}&posts_per_page=12");
} else if (count($author_ids == 0 ) && count($cat_ids) > 0) {
    $query = new WP_Query("cat={$cat_ids}&posts_per_page=12");
}

In this particular case I was looking for 12 posts, so if someone needs more, just change the number 12 at the bottom for the number they need.

I'm not sure why, but this line:

    $post_ids = array_slice($post_ids, 0, 12);

had to be changed to this:

    $post_ids = array_slice($post_ids, 0, 16);

because it was only printing 8 posts instead of 12, despite the logs saying that my $post_ids contained 12 post IDs. Anyways, this bit by itself shouldn't cause any problems for anyone else who looks to use it. Hopefully this helps or someone else knows of a better way to implement this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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