Related Posts Excluding Certain Categories

You can simple run a foreach loop and exclude the terms from your returned array of terms. Just a few notes

  • cat accepts a string or integer value, not and array

  • category__not_in should be an array

Here is a modified version of a related posts function I recently did here. Please go and check it out and check out how the $args parameter should be used, just remember, it has moved to the third paremeter in the new function 😉

Here is the code (I have commented the code so you can follow it, it is untested though and requires PHP 5.4+)

function get_related_posts( $taxonomy = '', $exclude = [], $args = [] )
{
    /*
     * Before we do anything and waste unnecessary time and resources, first check if we are on a single post page
     * If not, bail early and return false
     */
    if ( !is_single() )
        return false;

    /*
     * Check if we have a valid taxonomy and also if the taxonomy exists to avoid bugs further down.
     * Return false if taxonomy is invalid or does not exist
     */
    if ( !$taxonomy ) 
        return false;

    $taxonomy = filter_var( $taxonomy, FILTER_SANITIZE_STRING );
    if ( !taxonomy_exists( $taxonomy ) )
        return false;

    /*
     * We have made it to here, so we should start getting our stuff togther. 
     * Get the current post object to start of
     */
    $current_post = get_queried_object();

    /*
     * Get the post terms
     */
    $terms = get_the_terms( $current_post->ID, $taxonomy );

    /*
     * Lets only continue if we actually have post terms and if we don't have an WP_Error object. If not, return false
     */
    if ( !$terms || is_wp_error( $terms ) )
        return false;

    // Define our array to hold the term id'saveHTML
    $term_ids = [];
    foreach ( $terms as $term ) {
        // Check if we have terms to exclude and exclude them
        if ( $exclude ) {
            if ( in_array( $term->id, $exclude ) )
                continue;
        } else {
            $term_ids[] = (int) $term->term_id;
        }
    /*
     * Set the default query arguments
     */
    $defaults = [
        'post_type' => $current_post->post_type,
        'post__not_in' => [$current_post->ID],
        'tax_query' => [
            [
                'taxonomy' => $taxonomy,
                'terms' => $term_ids,
                'include_children' => false
            ],
        ],
    ];

    /*
     * Validate and merge the defaults with the user passed arguments
     */
    if ( is_array( $args ) ) {
        $args = wp_parse_args( $args, $defaults );
    } else {
        $args = $defaults;
    }

    return $q;
}

You would use it as follow then

if ( function_exists( 'get_related_posts' ) ) {
    $related_posts = get_related_posts( 
        'my_taxonomy_name', // Taxonomy name, if build in, in should be 'category'
        [1, 2, 3] // Array of terms to exclude
    );
    if ( $related_posts ) {
        foreach ( $related_posts as $post ) {
            setup_postdata( $post ); 
            // Use your template tags and html mark up as normal like
            the_title();
            the_content();
            // etc etc
        }
        wp_reset_postdata();
    }
}