Modify Category Queries in WordPress

WordPress has a lot of powerful tools that will speed up the development process by a great amount, the first one that you will encounter when creating themes is the WP_Query.

For those who are not aware of what is the WP_Query, you can assume its the WordPress tool that will give you control over the Queries made to fetch posts in the database.

On this article I will talk a little bit of how to change and modify your WordPress theme or plugin to allow better control over the WP_Query taxonomies Module, which gives the developer a very easy way to handle the Taxonomy related searches within WordPress.

The WP_Tax_Query

When you call for a new instance of the WP_Query with any mention to any taxonomy, as example Categories and Tags, WordPress internally will use WP_Tax_Query to create the SQL related to the Taxonomies.

Main Query vs new WP_Query

When you request a url inside of WordPress it will load a main WP_Query instance that will try to fetch the contents for the template that you are currently trying to see.

For the instances of the WP_Query that you fire inside of a template you will have the full control over which variables are going to be used, when dealing with the Main WordPress Query you will need to leverage the power of some actions or filters in order to be able to change which arguments are being used to build the SQL.

How to Change the WordPress Main Query

There is one action that is important when it comes to changing the main WordPress Query, pre_get_posts is an action that has 1 single variable attached to it, the $query, which is the WP_Query instance that is about to run.

It's very important to note, that this action will run before the SQL query to grab the posts for ANY WP_Query, so you will need to make sure that you are dealing with the main query before doing anything.

Exclude two Categories

On our first example we will have a few rules that should be considered before we change the $query instance.

  • If we are not in the main Query, we don't want to continue
  • When the requested page is one of the two categories archives
  • Don't do anything if we are inside of a single page
function bord_exclude_category( $query ) {
    // It's always better to look for negative then returning
    // Makes our code cleaner and with less indentation
    if ( ! $query->is_main_query() ) {
        return; // Actions on WordPress require no Return
    }

    // Excluded Categories
    $excluded_categories = array( 1, 5 ); 

    // Check for single page, like a Post or Attachment page - This will avoid posts from that category from giving a 404
    if ( $query->is_singular ){
        return;
    }

    // If we are on the Category Archive we don't want to exclude the posts
    if ( is_category( $excluded_categories ) ) {
        return;
    }

    // Here we do our magic
    $query->set( 'category__not_in', $excluded_categories );
}
add_action( 'pre_get_posts', 'bord_exclude_category' );

Link Two Categories together

Imagine the following scenario, I have two categories in WordPress that are almost the same and I want to make sure that whenever I'm searching for the Archive of the first One I will show results from the second.

In this case we will allow this structure to pass to any instance of the WP_Query not only the main.

function bord_link_categories( $query ) {
    // Excluded Categories
    $linked = array( 2, 3 ); 

    // If singular we don't care
    if ( $query->is_singular ){
        return;
    }

    // Check the possible varaibles
    // Don't forget to check if isset to avoid warnings
    if ( isset( $query->query_vars['cat'] ) ){
        $intersect = array_intersect( (array) $query->query_vars['cat'], $linked );

        if ( ! empty( $intersect ) ){
            $query->query_vars['cat'] = array_unique( (array) $query->query_vars['cat'] + $linked );
        }
    }

    // Check both the varaibles
    if ( isset( $query->query_vars['category__in'] ) ){
        $intersect = array_intersect( (array) $query->query_vars['category__in'], $linked );

        if ( ! empty( $intersect ) ){
            $query->query_vars['category__in'] = array_unique( (array) $query->query_vars['category__in'] + $linked );
        }
    }

    // If I'm excluding one of the Categories then Exclude the other
    if ( isset( $query->query_vars['category__not_in'] ) ){
        $intersect = array_intersect( (array) $query->query_vars['category__not_in'], $linked );

        if ( ! empty( $intersect ) ){
            $query->query_vars['category__not_in'] = array_unique( (array) $query->query_vars['category__not_in'] + $linked );
        }
    }
}
add_action( 'pre_get_posts', 'bord_link_categories' );

Restricted Category

You might see yourself in a situation where you will need to restrict the contents of a category based on a user capability, which can of course be done with the help of plugins, but for the sake of the demonstrations let's assume that you need to do it yourself.

Important Conditionals:

  • We don't care if it's the main query
  • Users need to have the permission to read_private_posts to see any of the content
<?php
function bord_restrict_categories( $query ) {
    $restricted = array( 8 ); 
    $capability = 'read_private_posts';

    // If user cant, then remove any posts from that category from the query
    if ( ! current_user_can( $capability ) ){
        if ( isset( $query->query_vars['category__not_in'] ) ){
            $query->query_vars['category__not_in'] = ( (array) $query->query_vars['category__not_in'] ) + $restricted;
        } else {
            $query->query_vars['category__not_in'] = $restricted;
        }
    }

}
add_action( 'pre_get_posts', 'bord_restrict_categories' );

Questions and Mistakes

If I made any mistakes on my codes above or said something that is wrong, please comment below and I will make sure to amend it.

Discussions — One Response

  • Alex March 22, 2016 on 4:54 am

    Great article! Thanks!

    Reply

Leave a Reply