0

Scenario

I am using a complex, awesomely crafted tax query to get up to 4 related posts, first going by tag, then by category if there are not enough posts in tag-related query. This was finely addressed here.

I am now trying to do another extension of this function to do a 3rd query when necessary that gets all "post-type related posts."


What I Have So Far

I need to do this because we use many custom post types which have no tags or category associations at all -- I did not realize this when looking for original solution and its needed because not only does this function currently break the bottom of the page on custom post_types which are not associated with a tag or a category, due to

Fatal error: Call to a member function have_posts() on a non-object in...

but of course also because without including the custom post_types, the Related Posts come out empty.

I have this so far but running into a snag and I can't tell why:

function get_max_related_posts( $recent_posts = '', $taxonomy_1 = 'post_tag', $taxonomy_2 = 'category', $total_posts = 4 ) {
    // First, make sure we are on a single page, if not, bail
    if ( !is_single() )
        return false;

    // Sanitize and vaidate our incoming data
    if ( 'post_tag' !== $taxonomy_1 ) {
        $taxonomy_1 = filter_var( $taxonomy_1, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy_1 ) )
            return false;
    }

    if ( 'category' !== $taxonomy_2 ) {
        $taxonomy_2 = filter_var( $taxonomy_2, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy_2 ) )
            return false;
    }

    if ( 4 !== $total_posts ) {
        $total_posts = filter_var( $total_posts, FILTER_VALIDATE_INT );
            if ( !$total_posts )
                return false;
    }

    // Everything checks out and is sanitized, lets get the current post
    $current_post = sanitize_post( $GLOBALS['wp_the_query']->get_queried_object() );

    // Lets get the first taxonomy's terms belonging to the post
    $terms_1 = get_the_terms( $current_post, $taxonomy_1 );

    // Set a varaible to hold the post count from first query
    $count = 0;
    // Set a variable to hold the results from query 1
    $q_1   = [];

    // Make sure we have terms
    if ( $terms_1 ) {
        // Lets get the term ID's
        $term_1_ids = wp_list_pluck( $terms_1, 'term_id' );

        $exclude = array_merge( [$current_post->ID], $recent_posts );

        // Lets build the query to get related posts
        $args_1 = [
            'post_type'      => $current_post->post_type,
            'post__not_in'   => $exclude,
            'posts_per_page' => $total_posts,
            'fields'         => 'ids',
            'tax_query'      => [
                [
                    'taxonomy'         => $taxonomy_1,
                    'terms'            => $term_1_ids,
                    'include_children' => false
                ]
            ],
        ];
        $q_1 = get_posts( $args_1 );
        // Count the total amount of posts
        $q_1_count = count( $q_1 );

        // Update our counter
        $count = $q_1_count;
    }

    // We will now run the second query if $count is less than $total_posts
    if ( $count < $total_posts ) {
        $terms_2 = get_the_terms( $current_post, $taxonomy_2 );
        // Make sure we have terms
        if ( $terms_2 ) {
            // Lets get the term ID's
            $term_2_ids = wp_list_pluck( $terms_2, 'term_id' );

            // Calculate the amount of post to get
            $diff = $total_posts - $count;

            // Create an array of post ID's to exclude
            if ( $q_1 ) {
                $exclude = array_merge( $exclude, $q_1 );
            }

            $args_2 = [
                'post_type'      => $current_post->post_type,
                'post__not_in'   => $exclude,
                'posts_per_page' => $diff,
                'fields'         => 'ids',
                'tax_query'      => [
                    [
                        'taxonomy'         => $taxonomy_2,
                        'terms'            => $term_2_ids,
                        'include_children' => false
                    ]
                ],
            ];
            $q_2 = get_posts( $args_2 );

            if ( $q_2 ) {
                // Merge the two results into one array of ID's
                $q_1 = array_merge( $q_1, $q_2 );
            }
            if ( $count < $total_posts ) {
                // Calculate the amount of post to get
                $diff = $total_posts - $count;

                // Create an array of post ID's to exclude
                if ( $q_2 ) {
                    $exclude = array_merge( $exclude, $q_2 );
                }

                $args_3 = [
                    'post_type'      => $current_post->post_type,
                    'post__not_in'   => $exclude,
                    'posts_per_page' => $diff,
                    'fields'         => 'ids'
                    ];
                $q_3 = get_posts( $args_3 );

                if ( $q_3 ) {
                    // Merge the two results into one array of ID's
                    $q_1 = array_merge( $q_1, $q_3 );
                }
            }
        }
    }

    // Make sure we have an array of ID's
    if ( !$q_1 )
        return false;

    // Run our last query, and output the results
    $final_args = [
        'ignore_sticky_posts' => 1,
        'post_type'           => $current_post->post_type,
        'posts_per_page'      => count( $q_1 ),
        'post__in'            => $q_1,
        'order'               => 'ASC',
        'orderby'             => 'post__in',
        'suppress_filters'    => true,
        'no_found_rows'       => true
    ];
    $final_query = new WP_Query( $final_args );

    return $final_query;
}

The Problem

I honestly can't tell what the problem is, as when I try

$args_3 = [
    'post_type'      => 'gear_post',
    'posts_per_page' => 5,
    'fields'         => 'ids'
    ];
$q_3 = get_posts( $args_3 );

On the live page, it out puts the proper post IDs which I need to feed into the final WP_Query.

In the above, long and sweet function, I think I am feeding the IDs properly:

            if ( $q_2 ) {
                // Merge the two results into one array of ID's
                $q_1 = array_merge( $q_1, $q_2 );
            }
            if ( $count < $total_posts ) {
                // Calculate the amount of post to get
                $diff = $total_posts - $count;

                // Create an array of post ID's to exclude
                if ( $q_2 ) {
                    $exclude = array_merge( $exclude, $q_2 );
                }

                // THIS IS THE 3rd QUERY that's I'm not getting right
                // THIS IS THE 3rd QUERY that's I'm not getting right
                // THIS IS THE 3rd QUERY that's I'm not getting right
                $args_3 = [
                    'post_type'      => $current_post->post_type,
                    'post__not_in'   => $exclude,
                    'posts_per_page' => $diff,
                    'fields'         => 'ids'
                    ];
                $q_3 = get_posts( $args_3 );

                if ( $q_3 ) {
                    // Merge the two results into one array of ID's
                    $q_1 = array_merge( $q_1, $q_3 );
                // THIS IS THE 3rd QUERY that's I'm not getting right
                // THIS IS THE 3rd QUERY that's I'm not getting right
                // THIS IS THE 3rd QUERY that's I'm not getting right
                }
            }
        }
    }

    // Make sure we have an array of ID's
    if ( !$q_1 )

Yet when I try to run the function on that page, I get this error:

Fatal error: Call to a member function have_posts() on a non-object in...

Any idea what I am doing wrong?

Solution - Final Version

Using Peter's sweet answer, I found a couple minor bugs for my use case and also extended the function a bit more, to include any random "related" posts if tags, categories, and post type all come up empty.

My final version is like so:

function get_max_related_posts( $recent_posts = array(), $taxonomy_1 = 'post_tag', $taxonomy_2 = 'category', $total_posts = 4 ) {
    // First, make sure we are on a single page, if not, bail
    if ( !is_single() )
        return false;

    // Sanitize and vaidate our incoming data
    if ( 'post_tag' !== $taxonomy_1 ) {
        $taxonomy_1 = filter_var( $taxonomy_1, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy_1 ) )
            return false;
    }

    if ( 'category' !== $taxonomy_2 ) {
        $taxonomy_2 = filter_var( $taxonomy_2, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy_2 ) )
            return false;
    }

    if ( 4 !== $total_posts ) {
        $total_posts = filter_var( $total_posts, FILTER_VALIDATE_INT );
            if ( !$total_posts )
                return false;
    }

    // Everything checks out and is sanitized, lets get the current post
    $current_post = sanitize_post( $GLOBALS['wp_the_query']->get_queried_object() );

    // Lets get the first taxonomy's terms belonging to the post
    $terms_1 = get_the_terms( $current_post, $taxonomy_1 );

    // Set a varaible to hold the post count from first query
    $count = 0;
    // Set a variable to hold the results from query 1
    $q_1   = [];
    // Set a variable to hold the exclusions
    $sticky = get_option( 'sticky_posts' );
    $exclude = array_merge( [$current_post->ID], $sticky );
    $exclude = array_merge( $exclude, $recent_posts );

    // Make sure we have terms
    if ( $terms_1 ) {
        // Lets get the term ID's
        $term_1_ids = wp_list_pluck( $terms_1, 'term_id' );

        // Lets build the query to get related posts
        $args_1 = [
            'post_type'      => $current_post->post_type,
            'post__not_in'   => $exclude,
            'posts_per_page' => $total_posts,
            'fields'         => 'ids',
            'tax_query'      => [
                [
                    'taxonomy'         => $taxonomy_1,
                    'terms'            => $term_1_ids,
                    'include_children' => false
                ]
            ],
        ];
        $q_1 = get_posts( $args_1 );

        // Update our counter
        $count = count( $q_1 );
        // Update our counter
        $exclude = array_merge( $exclude, $q_1 );
    }

    // We will now run the second query if $count is less than $total_posts
    if ( $count < $total_posts ) {
        $terms_2 = get_the_terms( $current_post, $taxonomy_2 );
        // Make sure we have terms
        if ( $terms_2 ) {
            // Lets get the term ID's
            $term_2_ids = wp_list_pluck( $terms_2, 'term_id' );

            // Calculate the amount of post to get
            $diff = $total_posts - $count;

            $args_2 = [
                'post_type'      => $current_post->post_type,
                'post__not_in'   => $exclude,
                'posts_per_page' => $diff,
                'fields'         => 'ids',
                'tax_query'      => [
                    [
                        'taxonomy'         => $taxonomy_2,
                        'terms'            => $term_2_ids,
                        'include_children' => false
                    ]
                ],
            ];
            $q_2 = get_posts( $args_2 );

            if ( $q_2 ) {
                // Merge the two results into one array of ID's
                $q_1 = array_merge( $q_1, $q_2 );

                // Update our post counter
                $count = count( $q_1 );

                // Update our counter
                $exclude = array_merge( $exclude, $q_2 );
            }
        }
    }

    // We will now run the third query if $count is less than $total_posts
    if ( $count < $total_posts ) {
        // Calculate the amount of post to get
        $diff = $total_posts - $count;

        $args_3 = [
            'post_type'      => $current_post->post_type,
            'post__not_in'   => $exclude,
            'posts_per_page' => $diff,
            'fields'         => 'ids',
        ];
        $q_3 = get_posts( $args_3 );

        if ( $q_3 ) {
            // Merge the two results into one array of ID's
            $q_1 = array_merge( $q_1, $q_3 );
        } else {
            $args_4 = [
                'post_type'      => 'any',
                'post__not_in'   => $exclude,
                'posts_per_page' => $diff,
                'fields'         => 'ids',
            ];
            $q_4 = get_posts( $args_4 );

            if ( $q_4 ) {
                // Merge the two results into one array of ID's
                $q_1 = array_merge( $q_1, $q_4 );
            }
        }
    }

    // Make sure we have an array of ID's
    if ( !$q_1 )
        return false;

    // Run our last query, and output the results
    $final_args = [
        'ignore_sticky_posts' => 1,
        'post_type'           => 'any',
        'posts_per_page'      => count( $q_1 ),
        'post__in'            => $q_1,
        'order'               => 'ASC',
        'orderby'             => 'post__in',
        'suppress_filters'    => true,
        'no_found_rows'       => true
    ];
    $final_query = new WP_Query( $final_args );

    return $final_query;
}

Mainly, I fixed the exclusions mechanism, extended in one more time to any posts, and also changed the "post_type" in the final query arguments to 'any' so that the final query is not filtered by the custom post type. Again, thank you Peter!

1

You should take your third query outside the first

// We will now run the second query if $count is less than $total_posts
if ( $count < $total_posts ) {

condition. You should also need to update the counter after the second query and rebuild the list to exclude posts already in the array of ID's. We al use the same logic as we have used in the first query to run the third one.

I have just copied my code over from my answer here and extended it. Again, this is untested, so it might be buggy. Use case stays exactly the same

function get_max_related_posts( $taxonomy_1 = 'post_tag', $taxonomy_2 = 'category', $total_posts = 4 )
{
    // First, make sure we are on a single page, if not, bail
    if ( !is_single() )
        return false;

    // Sanitize and vaidate our incoming data
    if ( 'post_tag' !== $taxonomy_1 ) {
        $taxonomy_1 = filter_var( $taxonomy_1, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy_1 ) )
            return false;
    }

    if ( 'category' !== $taxonomy_2 ) {
        $taxonomy_2 = filter_var( $taxonomy_2, FILTER_SANITIZE_STRING );
        if ( !taxonomy_exists( $taxonomy_2 ) )
            return false;
    }

    if ( 4 !== $total_posts ) {
        $total_posts = filter_var( $total_posts, FILTER_VALIDATE_INT );
            if ( !$total_posts )
                return false;
    }

    // Everything checks out and is sanitized, lets get the current post
    $current_post = sanitize_post( $GLOBALS['wp_the_query']->get_queried_object() );

    // Lets get the first taxonomy's terms belonging to the post
    $terms_1 = get_the_terms( $current_post, $taxonomy_1 );

    // Set a varaible to hold the post count from first query
    $count = 0;
    // Set a variable to hold the results from query 1
    $q_1   = [];

    // Make sure we have terms
    if ( $terms_1 ) {
        // Lets get the term ID's
        $term_1_ids = wp_list_pluck( $terms_1, 'term_id' );

        // Lets build the query to get related posts
        $args_1 = [
            'post_type'      => $current_post->post_type,
            'post__not_in'   => [$current_post->ID],
            'posts_per_page' => $total_posts,
            'fields'         => 'ids',
            'tax_query'      => [
                [
                    'taxonomy'         => $taxonomy_1,
                    'terms'            => $term_1_ids,
                    'include_children' => false
                ]
            ],
        ];
        $q_1 = get_posts( $args_1 );

        // Update our counter
        $count = count( $q_1 );
    }

    // We will now run the second query if $count is less than $total_posts
    if ( $count < $total_posts ) {
        $terms_2 = get_the_terms( $current_post, $taxonomy_2 );
        // Make sure we have terms
        if ( $terms_2 ) {
            // Lets get the term ID's
            $term_2_ids = wp_list_pluck( $terms_2, 'term_id' );

            // Calculate the amount of post to get
            $diff = $total_posts - $count;

            // Create an array of post ID's to exclude
            if ( $q_1 ) {
                $exclude = array_merge( [$current_post->ID], $q_1 );
            } else {
                $exclude = [$current_post->ID];
            }

            $args_2 = [
                'post_type'      => $current_post->post_type,
                'post__not_in'   => $exclude,
                'posts_per_page' => $diff,
                'fields'         => 'ids',
                'tax_query'      => [
                    [
                        'taxonomy'         => $taxonomy_2,
                        'terms'            => $term_2_ids,
                        'include_children' => false
                    ]
                ],
            ];
            $q_2 = get_posts( $args_2 );

            if ( $q_2 ) {
                // Merge the two results into one array of ID's
                $q_1 = array_merge( $q_1, $q_2 );

                // Update our post counter
                $count = count( $q_1 );
            }
        }
    }

    // We will now run the third query if $count is less than $total_posts
    if ( $count < $total_posts ) {
        // Calculate the amount of post to get
        $diff = $total_posts - $count;

        // Build an array of posts to exclude
        if ( $q_1 ) {
            $exclude = array_merge( [$current_post->ID], $q_1 );
        } else {
            $exclude = [$current_post->ID];
        }

        $args_3 = [
            'post_type'      => $current_post->post_type,
            'post__not_in'   => $exclude,
            'posts_per_page' => $diff,
            'fields'         => 'ids',
        ];
        $q_3 = get_posts( $args_3 );

        if ( $q_3 ) {
            // Merge the two results into one array of ID's
            $q_1 = array_merge( $q_1, $q_3 );
        }
    }

    // Make sure we have an array of ID's
    if ( !$q_1 )
        return false;

    // Run our last query, and output the results
    $final_args = [
        'ignore_sticky_posts' => 1,
        'post_type'           => $current_post->post_type,
        'posts_per_page'      => count( $q_1 ),
        'post__in'            => $q_1,
        'order'               => 'ASC',
        'orderby'             => 'post__in',
        'suppress_filters'    => true,
        'no_found_rows'       => true
    ];
    $final_query = new WP_Query( $final_args );

    return $final_query;
}
  • 1
    Incredible. Thank you so much. So simple! Doh, right? :P Honestly no idea why I decided to try and shove it inside the second if statement. I tend to do that - overcomplicate it by thinking I need to embed sequential if statements when they are already sequential. Anyway, Thank you again, works flawlessly. – Andre Bulatov Mar 4 '16 at 18:54

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.